Indie game storeFree gamesFun gamesHorror games
Game developmentAssetsComics

Internet Janitor

A member registered Aug 02, 2018 · View creator page →

Creator of

Recent community posts

This is really slick and well-executed. I'm glad I was able to help you realize your vision here!

The `view[]` event can be fired on a card for several reasons, which aren't necessarily just the first time you visit a card. It's better to think of it as an event for refreshing the contents of the card, and you'll need some extra logic if you want to ensure it only ever fires once. This thread describes a few possible alternatives.

One additional approach not described in that thread would be to invent a new event, say "visit[]" in the card script:

on visit do[deck]
 # ... etc ...

And then send a "visit[]" event to the card in any script that navigates to the card. For example,

on click do

Since we just made up the "visit" convention ourselves, Decker will never fire it automatically, and we can make it happen precisely when it's appropriate, but all the logic for what to do "on visit" lives on the card itself, which can help keep things logically organized.

Does that make sense?

(1 edit)

The block of code above starting with "%%CRD0" is what a card looks like in the system clipboard. If you select and copy the entire thing from your web browser, in Decker you should be able to choose "Edit -> Paste Card" from the menu and then have the entire example to play with interactively and modify.

It might be best to tackle some simpler projects that involve scripting before diving in the deep end. There are lots of useful examples and discussions here in the community forum. In particular, it might be worth trying the examples in these tutorials to gain some familiarity with adding interactivity to decks:

Make your own Chicken Generator:

Building a Font Importer:

Scripting One-Liners:


Decker is designed to work equally well on desktops, in web browsers, and on multitouch devices that don't necessarily have a keyboard, so input events are limited to a common intersection between the capabilities of all these devices. You can't obtain raw "keyup"/"keydown" events, but it is possible to capture arrow key presses (or their equivalent "Nav Gesture" directional swipes on a touch device) through the "navigate[]" event, and you can assign alphanumeric keyboard keys as shortcuts for clicking on buttons. General mouse input is available through the Pointer interface and the "click[]", "drag[]" and "release[]" events provided by Canvas widgets, though since it's trying to represent the intersection between mice and touch gestures, input is limited to a single button.

Sokoban is a good fit for the constraints of Decker's keyboard input, since everything is turn-based. A classical Nethack-esque roguelike might also work well, or many types of puzzle game. Some action games could also work: in Pac-Man or the puzzle-platformer Lode Runner, the player character typically continues moving in one direction until a player provides a new key input. You could also design action games purely around pointer input; I could imagine something like Joust using the mouse to control the player's direction and clicking to make your mount jump and flap.

Making a platformer is a pretty complicated project no matter how you slice it, let alone one with good "gamefeel", but here's a very basic scrap of a starting-point: a card with an Animated Canvas as a display, a field to store a JSON representation of the game state (player position and velocity, to begin with; you can make this field invisible when you aren't debugging), and a card-level view[] handler that updates the state and redraws the canvas at 60FPS based on the pointer:

You can copy and paste this snippet below to try it yourself:

%%CRD0{"c":{"name":"home","script":"on view do\n st:\"%j\" parse state.text\n \n grounded:!screen.size[1]>st.pos[1]\n relpos:pointer.pos-(screen.pos+st.pos)\n vy:if pointer.down&grounded -20 else st.vel[1] end\n vx:if -20>relpos[0] -2 elseif relpos[0]>20 2 else 0 end\n \n vy    :vy+1                  # gravity\n st.vel:.9*vx,vy              # friction\n st.pos:st.pos+st.vel         # inertia\n st.pos:0|screen.size&st.pos  # solid walls/floor\n \n screen.clear[]\n screen.rect[st.pos-0,10 10,20 \"center\"] \n \n state.text:\"%j\" format st\nend","widgets":{"screen":{"type":"canvas","size":[195,186],"pos":[159,73],"animated":1,"volatile":1,"scale":1},"state":{"type":"field","size":[100,121],"pos":[379,101],"value":"{\"vel\":[0,8.891043],\"pos\":[3.6,186]}"}}},"d":{}}

We could all use a Bunkus in our lives; a real good egg.

If you import images into Decker (via drag-and-drop or Import Image), Decker interprets them as still images, decoding the first frame of GIFs and then applying dithering to convert them to 1-bit. Decker itself does not have any built-in ability to represent animated images.

There are, however, Contraptions that extend Decker with animated GIF playback (among many other things). See the linked thread for several variations on a GIF importer/player, some of which support color. Decker's palette will need to be configured to match the palette you used in WigglyPaint to get all the colors correct when you re-import the GIFs; as long as the images were all made using the same palette, you should be able to simply save WigglyPaint to your desktop, drag-and-drop the deck onto Decker and use the Font/DA Mover to import the "patterns" resource.

While I'm afraid it doesn't help much if you've already saved your drawings as GIFs,  for future reference the easiest way to import animations from WigglyPaint into Decker is to not export GIFs, but instead copy the "wiggler" contraption directly from WigglyPaint into decker, as demonstrated in this thread.

Does that make any sense?

It is not possible for an unmodified copy of web-decker to programmatically fetch external files hosted e.g. somewhere on github pages, make http requests, etc. Doing this sort of thing would require adding custom javascript extensions to web-decker. An example of how such an extension might be written can be found in this subthread.

In general, decks should be self-contained objects pre-packed with any data they require to operate. A deck is a database, with grid widgets for storing tables and Lil for querying and manipulating them.

Lilt can be used to import or export data to or from a .deck or .html file in an automated fashion. If you insist on storing some dataset independently from a deck that uses it, it might be possible to use Lilt as part of a continuous integration pipeline or "build script" to bake out an updated web-decker build when an associated csv file is changed.

The colors have been accessible from the toolbar for quite a while. If you choose any drawing tool, and then select "Style -> Color" from the menu, you'll get a color palette instead of the 1-bit palettes in the toolbar. Works for the modal color picker as well:

This July, it's another Decker game jam!

Start pondering what sorts of creations you'll make, and please help spread the word!

Feel like your life just isn't sliding into place? Experience catharsis with

ThatPuzzle Contraption

The ThatPuzzle contraption is a classic sliding tile puzzle often called a "15-puzzle", in contraption form. Configure its dimensions from 2x2 to 10x10 tiles, and it will automatically display an image sliced up from the card background it's placed over. There are even event hooks and attributes for recognizing a successful solution!

  • thatpuzzle.dim : r/w integer from 2 to 10; the number of tiles per axis.
  • thatpuzzle.solved: read-only bool; is the puzzle currently solved?
  • thatpuzzle.reset[] : reset the tiles to a solved configuration.
  • thatpuzzle.scramble[] : randomize the tiles. for large puzzles you may need to repeat this a few times for a thorough mixing.
  • on change do .. end : fired whenever a user moves a tile.
%%WGT0{"w":[{"name":"thatpuzzle1","type":"contraption","size":[100,100],"pos":[199,126],"script":"on change do\n won.value:me.solved\nend","def":"thatpuzzle","widgets":{"c":{},"s":{"value":"[]"},"dim":{"value":"3"}}}],"d":{"thatpuzzle":{"name":"thatpuzzle","size":[100,100],"resizable":1,"margin":[5,5,5,5],"description":"the classic sliding-tile puzzle. you know, that one.","script":"BG:image[\"%%IMG2AAYABgEIDQMBAiABAQENAgECIAIBBCACAQEMAQEH\"]\nFG:image[\"%%IMG2AAkACQEGAAMBASAEAQIAAgEBIAQBASABAQEAAQEBIAQBASACAQIgBAEBIAIBByACAQEAAQEBDQQBASABAQEAAgEBDQQBAgADAQY=\"]\nBG_M:4,4,1,1\nFG_M:4,4,4,4\nDELTAS:\"%j\" parse \"[[-1,0],[0,-1],[1,0],[0,1]]\"\nSTEPS:30\n\non get_dim do 0+dim.text end\non set_dim x do dim.text:10&2|x end\n\non swap a x y do t:a[x] a[x]:a[y] a[y]:t a end\non initstate do d:get_dim[] (d^2)%1+range d^2 end\non setstate x do s.text:\"%j\" format list x end\non getstate do\n d:get_dim[]\n r:\"%j\" parse s.text\n if (d^2)~count r r else initstate[] end\nend\n\non view do\n d:get_dim[]\n b:getstate[]\n cell:c.size/d\n c.segment[BG 0,0,c.size BG_M]\n o:d cross d\n tiles:select orderby p asc where v from select v:b p:o from ()\n each row in rows tiles\n  c.segment[FG (row.p*cell),(cell+2 drop FG_M) FG_M]\n  bg:deck.card.image.copy[card.pos+o[row.v-1]*cell (cell,cell)-1]\n  c.paste[bg (1+row.p*cell)]\n end\nend\n\non click pos do\n d:get_dim[]\n b:nb:getstate[]\n o:(d cross d) dict range d^2\n p:floor pos/c.size/d\n each delta in DELTAS\n  t:p+delta\n  if (0~b[o[t]])&(!max(t<0)|(t>d-1)) nb:swap[b o[t] o[p]] end\n end\n setstate[nb]\n view[]\n if !nb~b card.event[\"change\"] end\nend\n\non get_reset do\n on reset do\n  setstate[()]\n  view[]\n end\nend\n\non get_scramble do\n on scramble do\n  d:get_dim[]\n  b:getstate[]\n  o:(d cross d) dict range d^2\n  p:first extract key where 0=b from o\n  each in range 100\n   t:p+random[DELTAS]\n   if !max(t<0)|(t>d-1) b:swap[b o[t] o[p]] p:t end\n  end\n  setstate[b]\n  view[]\n end\nend\n\non get_solved do\n getstate[]~initstate[]\nend","template":"on change do\n \nend","attributes":{"name":["dim"],"label":["Dim"],"type":["number"]},"widgets":{"c":{"type":"canvas","size":[100,100],"pos":[0,0],"locked":1,"volatile":1,"scale":1},"s":{"type":"field","size":[100,20],"pos":[117,13],"show":"none"},"dim":{"type":"field","size":[100,20],"pos":[117,47],"show":"none","value":"4"}}}}}

Currently the closest Decker has to what you seem to be describing is that with any widget selected, you can choose "Edit ->Copy Image" to copy an image of the current appearance of the selected widget to the clipboard. If you make a borderless, transparent field widget with the text/font/etc you want and copy the image, you can then paste it to the card background and manipulate it with drawing tools however you like.

When you open the "Action..." dialog, Decker will attempt to "unpack" any script already attached to the button and reflect it in the settings you see in that dialog, and when you confirm it writes an entirely new script. This dialog exists purely as a convenience for composing simple scripts without writing code, and is not intended to manipulate or append to arbitrary user-generated scripts.

(2 edits)

You can find documentation for format strings in Lil, The Formatting Language.

The format string of a slider widget is only given its value, which would usually be displayed as an integer (%i) or a floating-point number (%f); the rest of the string could, for example, provide units, like "%f degrees". In this way you *could* hardcode a maximum into the format string, like "%i/20" for an integer between 1 and 20 to be displayed like "7/20".

Extending the current behavior of sliders to make min/max and possibly other attributes of the widget available for formatting could be an interesting feature; I'll mull it over. For now, including that sort of information would require scripting. If you need this functionality in more than one place, perhaps you could use The Enum Contraption as a starting point?

What you're describing sounds like it ought to work. I would recommend making sure you have the script on the correct widget and checking carefully for typos. If it helps, here's a GIF of building the arrangement you describe from scratch:

It is not possible to embed widgets in rich text fields: they can only contain text (in multiple fonts), some of which can be hyperlinks, and inline images. With some fancy scripting and/or animated patterns it is possible to animate inline images within rich text. Here's an example with an "animated" field:

The script on the field:

on view do
 i:first extract arg where arg..type="image" from me.value

If you want to create the appearance of widgets within a rich text field, there are a variety of ways you might approach it. You could simply draw a card background that looks like interspersed text and widgets. (Using "Edit -> Copy Image" on widgets copies an image of them to the clipboard that you can then paste to the background and edit with drawing tools.) You could position widgets on top of a rich text field. You could even build Contraptions that contain other widgets (or images of them) stacked on top of a rich text field. I would recommend starting simple.

Yes. Just to be clear, if you make an instance of the EggTimer contraption and edit it's script, it will have a template like

on finish do

You can then fill in an event handler for "finish" like you would any script. For example,

on finish do

Note that if the only thing you want to do is wait 10 seconds after clicking a button before navigating to another card you could use the sleep[] function on an ordinary button's script:

on click do
 sleep[10 * 60]

The difference being that sleep[] is "synchronous" and prevents the user from doing anything else while the script sleeps, whereas the EggTimer is "asynchronous" and the user could click something else or navigate away from the timer before it goes off.

We can use a different cell size with some relatively small changes. Here's a 6x8 bitmap font I was able to track down:


Let's start by generalizing the "Import Sheet" script to adjust "img" to suit the dimensions of the imported bitmap. As an extra precaution to suit this example image, I'll also use[] to convert any white pixels (pattern 32) into transparent pixels (pattern 0):

on click do
 i:read["image"].map[32 dict 0]

Note that the size of images and widgets (the .size attribute) is given as a (width,height) pair instead of as separate ".x" and ".y" fields. In Lil, arithmetic operators like + and * generalize to work between single numbers (scalars) and lists of numbers:

These operators also generalize to work between two lists, pairing up every corresponding element of the lists:

(2,3)+(10,20)    # (12,23)

Given the size of an image, if we assume three rows of 32 characters, we can compute the cell size with a single division:

cell:img.size/(32,3)     # (6,8)

This process of generalization between numbers and lists of numbers is called "Conforming", and is described in more detail in the Lil reference manual.

In our "Create" button's script, we use the "cross" operator to create a list of coordinate pairs for every cell of the grid:

32 cross 3
# ((0,0),(1,0),(2,0),(3,0),(4,0),(5,0),(6,0),(7,0),(8,0),(9,0),(10,0),(11,0),(12,0),(13,0),(14,0),(15,0),(16,0),(17,0),(18,0),(19,0),(20,0),(21,0),(22,0),(23,0),(24,0),(25,0),(26,0),(27,0),(28,0),(29,0),(30,0),(31,0),(0,1),(1,1),(2,1),(3,1),(4,1),(5,1),(6,1),(7,1),(8,1),(9,1),(10,1),(11,1),(12,1),(13,1),(14,1),(15,1),(16,1),(17,1),(18,1),(19,1),(20,1),(21,1),(22,1),(23,1),(24,1),(25,1),(26,1),(27,1),(28,1),(29,1),(30,1),(31,1),(0,2),(1,2),(2,2),(3,2),(4,2),(5,2),(6,2),(7,2),(8,2),(9,2),(10,2),(11,2),(12,2),(13,2),(14,2),(15,2),(16,2),(17,2),(18,2),(19,2),(20,2),(21,2),(22,2),(23,2),(24,2),(25,2),(26,2),(27,2),(28,2),(29,2),(30,2),(31,2))

Instead of scaling every number in this list of pairs by 8, we now want to scale the first of each of these pairs by 6 and the second of each of these pairs by 8.

The "flip" operator transposes the x and y axes of a matrix (a list of lists). Given a list of (x,y) pairs, it produces a pair of lists: the first containing x coordinates, the second containing y coordinates:

flip 32 cross 3
# ((0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31),(0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2))

We can then multiply this pair of lists by our cell size, and then "flip" again to turn them back into a list of (x,y) pairs. In a more conventional language you'd probably have to write a loop (or two) to built this list of coordinates. Learning to use conforming to your advantage is the secret to concise and fast Lil.

cell * flip 32 cross 3
# ((0,6,12,18,24,30,36,42,48,54,60,66,72,78,84,90,96,102,108,114,120,126,132,138,144,150,156,162,168,174,180,186,0,6,12,18,24,30,36,42,48,54,60,66,72,78,84,90,96,102,108,114,120,126,132,138,144,150,156,162,168,174,180,186,0,6,12,18,24,30,36,42,48,54,60,66,72,78,84,90,96,102,108,114,120,126,132,138,144,150,156,162,168,174,180,186),(0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16))
flip cell * flip 32 cross 3
# ((0,0),(6,0),(12,0),(18,0),(24,0),(30,0),(36,0),(42,0),(48,0),(54,0),(60,0),(66,0),(72,0),(78,0),(84,0),(90,0),(96,0),(102,0),(108,0),(114,0),(120,0),(126,0),(132,0),(138,0),(144,0),(150,0),(156,0),(162,0),(168,0),(174,0),(180,0),(186,0),(0,8),(6,8),(12,8),(18,8),(24,8),(30,8),(36,8),(42,8),(48,8),(54,8),(60,8),(66,8),(72,8),(78,8),(84,8),(90,8),(96,8),(102,8),(108,8),(114,8),(120,8),(126,8),(132,8),(138,8),(144,8),(150,8),(156,8),(162,8),(168,8),(174,8),(180,8),(186,8),(0,16),(6,16),(12,16),(18,16),(24,16),(30,16),(36,16),(42,16),(48,16),(54,16),(60,16),(66,16),(72,16),(78,16),(84,16),(90,16),(96,16),(102,16),(108,16),(114,16),(120,16),(126,16),(132,16),(138,16),(144,16),(150,16),(156,16),(162,16),(168,16),(174,16),(180,16),(186,16))

Here's a complete modified script for the "Create" button that should work for any size of monospaced font with the correct 32x3 grid:

on click do
 f:deck.add["font" cell name.text]
 each p i in flip cell * flip 32 cross 3
  f[i]:img.copy[p cell]

Does that help?

Contraptions are independent, encapsulated units. Events generated inside a contraption do not automatically bubble up to a deck script in the way  events bubble up from widget -> card -> deck. Functions declared in a deck script (or for that matter a card or widget script) are not directly accessible as attributes. By designing contraptions not to specifically depend on the structure of the deck, they're more reusable.

You can, however, explicitly send an event to the deck with "deck.event[]". Given your deck-level declaration, from anywhere you have access to "deck" you can do something like

deck.event["stat_to_mod" 20]

Which will return "5". Using the same approach for sending events to cards or widgets can be extremely useful. For example, simulating a user's click on a button:


Does that make sense?

Another idea for making this configurable might be to use eval[]. For example, if you have an expression as a string (which could be a contraption attribute):


You could then evaluate it under a dictionary of variable bindings:

(x+floor eval[expr ("n" dict grid1.cellvalue)].value) ...

FYI, Decker 1.42 adds an extra item in the Edit menu for "Copy Rich Text" (ctrl/cmd + r), which will hopefully make working with formatting in fields more convenient.

(1 edit)

The "gif" contraption is designed to read gifs as a series of grayscale frames and dither them.

We could make it handle color by replacing that dithering code, which is in the script of the "b" button,

 grays:(0,1,32+range 16) dict "%j" parse g
 f.value:raze each i in read["image" "frames"].frames
  rtext.make["" ""[grays].transform["dither"]]

With something based on the equivalent in the "scrubber" contraption:

 f.value:raze each i in read["image" "frames"].frames
  rtext.make["" "" i]

All together as a copyable snippet:

%%WGT0{"w":[{"name":"gif1","type":"contraption","size":[100,100],"pos":[206,121],"def":"colorgif","widgets":{"c":{},"f":{},"b":{}}}],"d":{"colorgif":{"name":"colorgif","size":[100,100],"resizable":1,"margin":[0,0,0,0],"description":"Import and play animated 16-color gifs. Careful: huge gifs can quickly bloat the size of your deck!","script":"on view do\n fr:extract arg where arg..type=\"image\" from f.value\n\n c.clear[]\n if count fr\n\"none\"\n  i:fr[(count fr)*60]\n  card.size:i.size\n  c.paste[i]\n else\n\"solid\"\n end\nend","widgets":{"c":{"type":"canvas","size":[100,100],"pos":[0,0],"locked":1,"animated":1,"border":0,"scale":1},"f":{"type":"field","size":[73,35],"pos":[8,-50],"locked":1},"b":{"type":"button","size":[69,20],"pos":[16,40],"script":"on click do\n f.value:raze each i in read[\"image\" \"frames\"].frames\n  rtext.make[\"\" \"\" i]\n end\n view[]\nend","text":"Open Gif..."}}}}}

Note that this is still ultimately limited by Decker's 16-color palette; colors not in the palette are converted into the current palette via a minimum-squared-distance heuristic, which doesn't always do a fantastic job.

For best results you might need to customize Decker's palette and/or use external tools like imagemagick to adjust the GIF's palette. If you just want a little motion and color in your decks, you could instead directly import the "wiggler" contraption from wigglypaint.

Does any of that help point you in the right direction?

I suggest trying whatever approach you find the clearest and most straightforward.

If you run into performance issues I'd be happy to take a look and suggest alternatives and/or see if there's low-hanging fruit in Decker itself for speeding things up.

(2 edits)

You can disable (or alter) the sorting behavior by overriding the default "order" event handler; for example giving the grid a script like

on order do

For the record, the default handler currently looks like:

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

The cell-editing problem was reported by another user earlier today; this will be patched in the v1.42 release tomorrow. The problem is specifically caused by the default "changecell" handler not properly accounting for columns that don't have a format configured.

The "random[]" function can be called in several different ways to produce different kinds of random values. In your case, you want to select a random value from a list of possibilities and then supply that value to the "go[]" function.

The "go[]" function can navigate to cards by index (a number), by name (a string), or by value (a Card interface). We'll go with the latter. Within a script, all the cards of the deck are available as variables of the same name. Commas (,) between values are used to form a list. If we have cards named "cardA", "cardB", and "cardC", and we wish to navigate to one of those cards randomly when a button is clicked, we could give it a script like:

on click do

Does that help?

You might also find some of the examples in this tutorial illustrative:

Sounds great! I'd be happy to review drafts of any tutorial materials you write.

You can't embed any sort of arbitrary HTML in a deck. What are you trying to do?

Rich-text fields can contain links, which can be hyperlinks to web pages (must include an http:// or https:// prefix), hyperlinks to cards within the same deck (by name), or custom scripted behaviors equivalent to clicking a button widget (by overriding the field's "on link" handler).

(4 edits)

To "collapse" result rows within each group, perform an aggregation (any expression that produces a single value instead of a list) when you select result columns. For example,

 select job:first job by job from people
| job          |
| "Developer"  |
| "Sales"      |
| "Accounting" |

Note that if you're producing multiple result columns, you need to aggregate every column in some way to collapse the group. Otherwise, the aggregations will be "spread" to match the length of the other columns:

 select job:first job experience:sum age by job from people
| job          | experience |
| "Developer"  | 99         |
| "Sales"      | 28         |
| "Accounting" | 43         |
 select job:first job experience:age by job from people
| job          | experience |
| "Developer"  | 25         |
| "Developer"  | 40         |
| "Developer"  | 34         |
| "Sales"      | 28         |
| "Accounting" | 43         |

And if all you want is a single list of results, you can use "extract":

 extract first job by job from people

To order results by the grouping column, you probably want to order values _before_ grouping; otherwise you're ordering _within_ groups, which appear in the result in order of their original appearance. Clauses execute right-to-left, so:

 select job:first job by job orderby job asc from people
| job          |
| "Accounting" |
| "Developer"  |
| "Sales"      |

Decker's web exports are self-contained monolithic .html files, with no required "server-side" component; they can be served statically as part of any web page. Web-Decker strictly executes on the viewer's web browser. It is possible for scripts to prompt users to select and open files from their local filesystem, but there is no mechanism for referencing or fetching external files automatically, irrespective of where the deck is stored. In general, if a deck needs data, the data should be embedded in the deck.

While Decker's UI is primarily black and white, internally it uses a 16-color palette. You can draw with any of these colors on a card background by switching to "color mode" (Style -> Color). The palette is customizable, and there has been some previous discussion about importing color images.

Decks are, in a real sense, databases already, with grid widgets serving as reified tables and Lil offering a SQL-ish query language. There are no hard limits to the quantity of data which can be stored in a grid widget; tens of thousands of rows and a few dozen columns should be no trouble at all. With a huge dataset your mileage may vary, but you can always give it a shot.

Decker does not directly integrate with SQLite, but there are a few options. If you simply want to synchronize the contents of a deck with a SQLite database, you could use a Lilt script to automate exporting information from a deck or importing data and programmatically building a deck from it. If you're comfortable building Decker from source you could alternatively use The Danger Zone to communicate with other software live.

Decker can readily work with a variety of common data interchange formats like CSV, JSON, or XML, and it does have facilities for parsing binary formats.  There aren't currently any libraries for working with SQLite's database format, but it would certainly be possible for a sufficiently motivated individual to write one in Lil.

Decker's equivalent to HyperCard's "backgrounds" is Contraptions; a Contraption instance can be the size of an entire card, if you like, or you could compose forms from several contraptions.

Duplicating template cards with deck.copy[] and deck.paste[] is also a valid approach. This might be desirable if you want to start with a template and then customize each instance beyond just the data they contain.

Another approach entirely would be to use a few Grid widgets to store tabular data in one place, instead of spreading it across many cards. See the CRUD demo for a simple example of this pattern. Storing data in grids makes it much easier to use Lil's query language to manipulate it.

Does any of that point you in the right direction?

Decker community · Created a new topic Decker Sokoban

I wrote a basic Sokoban as a demonstration of how to use Decker to make turn-based puzzle games:

Try it here!

The game takes place on a single card, using the "navigate[]" event to capture arrow key presses and buttons with shortcut keys for reset/undo. Level data is stored in a series of other cards, with goals and passability information encoded directly into the background image as specific patterns. The player and boxes are represented with instances of a Contraption called a "mover" which encapsulates tweened animation and cycling through animation frames.

Feel free to pick this example apart and use it as a starting point for your own creations

Widgets have an attribute called "show" which controls their visibility; this can be "solid" (the default), "invert" (an alternate color-scheme), "transparent", or "none".

Suppose the button X is on card C1 and the button Y is on card C2.

You could give X a script for toggling Y's visibility:

on click do

Or a simpler version that just makes Y visible:

on click do"solid"

There are several other examples of doing this sort of thing in this thread.

To make clicking a button "unlock" dialogue or behavior elsewhere in the deck, you'll need to remember that the button has been clicked, and consult that record at a later time.

Checkboxes are just a special visual appearance for a button, so every button widget has a "value" attribute that can store a single boolean (0 or 1) value. Thus, we can use the button itself to keep track of whether it's been clicked. Suppose you've given the button Z on card C1 a script like so:

on click do

A script on another card could reset Z's value,


Or test its value in a conditional:

if C1.widgets.Z.value
  # do something special

If you have lots of "flags" like this, it may be a good idea to centralize them in some kind of "backstage" card so you can keep track of them easily, and perhaps to give yourself a script for resetting the state of the deck.

Does that make sense?

If you find it uncomfortable it's fairly easy to customize. By switching to Decker's editing tools and hiding all the "widgets" that make up most of the application you can redraw the background using some other pattern from the tool palettes, as shown below:

If you hold "shift" while using the arrow keys you can also move in increments of the grid size (16x16 by default)

For years, fans have clamored for a way to experience the pleasures of Charlie's Playhouse on modern computers. Through the use of advanced technology and cunning extrapolation I have created a compelling simulacrum with many of the thrills, qualities, and activities of the original, including:

  • "Jamming out" in the Music Room
  • Expressing your creativity with shareable coloring book pages
  • Hangings
  • Rapid-fire arithmetic
  • Sudden loud noises and flashing images

...and so much moreTry it now in your browser!

(This game is a humorous, if rather dark, riff on a very old piece of edutainment software. Don't expect much in the way of plot, but taking it apart to see how the minigames and game logic work might be highly educational! Charlie's Playhouse makes use of Dialogizer, Puppeteer, and Public Transit.)

(2 edits)

Not quite; that will also fire whenever the contraption is first displayed.

You probably want to detect the moment the last decrement occurs.

I made a simple version of this idea by creating a Contraption that consists of an invisible button layered on top of a "bar"-styled slider. When the button is clicked, it sets the slider to the maximum possible value and makes it "animated", so that it will tick down naturally. While the animation is in progress, the button is locked:

A paste-able version of this contraption is here:

%%WGT0{"w":[{"name":"cooldown1","type":"contraption","size":[100,25],"pos":[206,201],"def":"cooldown","widgets":{"s":{},"b":{},"l":{}}}],"d":{"cooldown":{"name":"cooldown","size":[100,25],"resizable":1,"margin":[5,5,5,5],"description":"a button which requires a configurable cooldown between clicks.","script":"on get_text   do l.text end\non set_text x do s.format:l.text:x end\non get_cooldown   do last s.interval end\non set_cooldown x do s.interval:0,1|x end\n\non view do\n s.font:card.font\n s.format:l.text\n if s.value\n  s.animated:1\n  s.value:s.value-1\n  b.locked:1\n else\n  s.animated:0\n  b.locked:0\n end\nend\n\non click do\n s.value:last s.interval\n card.event[\"click\"]\n view[]\nend","template":"on click do\n \nend","attributes":{"name":["text","cooldown"],"label":["Text","Cooldown"],"type":["string","number"]},"widgets":{"s":{"type":"slider","size":[100,25],"pos":[0,0],"locked":1,"interval":[0,100],"format":"","style":"bar"},"b":{"type":"button","size":[100,25],"pos":[0,0],"style":"invisible"},"l":{"type":"field","size":[100,20],"pos":[111,0],"show":"none"}}}}}

How's that?

As written, this example doesn't include a "disabled" mode for the cooldown buttons, which might be useful for some applications. It is resizable, and respects the .font attribute.  You could make a much fancier-looking version of this idea- possibly with color, etc- by using a canvas as the contraption's background and drawing the progress bar from scratch.

Glad you were able to resolve your problem!

Be aware that importing modules into your decks adds a copy of the module to your deck; occasionally Decker release notes may point out updates to Dialogizer, Puppeteer, or other modules that are included with Decker. If you want to be certain you're using the latest version of a module, you can use the Resources dialog to remove dd/pt/etc and then re-import them.

Carefully check the spelling and capitalization of the positions you're specifying against the diagram of stage directions, and that the arguments to Puppeteer's commands are in the correct order. For example, !move takes a POS as its second argument and does not alter EMOTE, while !anim does not take a POS argument or an EMOTE argument.

There are a number of working examples in the documentation; you could try changing them bit by bit to approach your use-case. If you're getting yourself confused by a complicated project, try making a new deck from scratch and assembling the smallest possible example that demonstrates the problem you observe.

If you want to issue multiple commands with one call to pt.command[], the second argument will need to be a list of strings. Placing a comma between strings joins them together into a list, even if they're spread across multiple lines:

 "!show NAME1 EMOTE customPOS1",
 "!show NAME2 EMOTE customPOS2"

The above is equivalent to:

pt.command[deck ("!show NAME1 EMOTE customPOS1","!show NAME2 EMOTE customPOS2")]

I think I see the problem. You've defined an "on command ..." function for the card, and defined some custom commands. This function will capture any "command" events sent to the card, which means the deck-level function that normally passes unhandled commands along to puppeteer:

on command x do pt.command[deck x] end

Doesn't get a chance to do its work!

You'll need to add a final "else" to the command handler on your card and pass unhandled events on to Puppeteer. You can do this directly:

on command x do
 if x~"mycommand1"
 elseif x~"mycommand2"
  pt.command[deck x]

Or you can use "send" to bubble the event up to the deck-level definition:

on command x do
 if x~"mycommand1"
 elseif x~"mycommand2"
  send command[x]

If you've added more "global" custom commands to the deck-level handler- in addition to those defined on the card- you'll want to use "send". Make sense?

Currently, copying text from a field always copies the plain-text equivalent of the selection. I'll give some thought to an alternative for rich text; perhaps an alternate "Copy Rich Text" menu item/shortcut.