Log In  
:: Unfold ::

WYSIWYG control code editor

Cart #wysiwyg-1 | 2023-04-04 | Code ▽ | Embed ▽ | License: CC4-BY-NC-SA

Hey there!

Tired of manually writing that long, one-line print statement, filled with control codes in order to layout static text like in a title screen, painstakingly modifying \^j and \+ values to position the text just right, adjusting \^x and \^y values of solid-filled text to draw background elements instead of using rectfill to save precious tokens, and then deciphering all of that when you need to go back and edit something? ...no? just me?

Did you know that with P8SCII control codes, you can change things like text color, size, screen position and more? Meaning, for static (and relatively positioned – camera offsets will apply) text, you never really need more than 1 argument to print, and no more than one print per layout!

For example, print("hello",8,8,5); print("world",16,16) can be written as ?"\^j22\f5hello\^j44world", saving tokens and characters.

However, there's a lot to remember with P8SCII, several potential snags, and it can get pretty unwieldy pretty quickly. That's why I made this WYSIWYG tool!

tool usage

To load in immediate mode, run load #wysiwyg, or run the cart above.

Hopefully the UI is fairly intuitive. See a demo below:

Basic usage:

  • Click the plus in the layers tab to add a new text box
  • With a layer selected (by default new layers are selected, but you can also select by clicking on it in the preview or in the layers tab):
    • click the edit icon (pencil icon) to edit it
    • use arrow keys (when not in the editing tab) or drag it with your mouse to move it
  • Press the tab key to show/hide the sidebar
  • Scroll through the layers list
  • Click on a selected background color again to set it to none
  • Before clicking "load from clipboard", paste (ctrl-v)

Note that there is no undo, so be careful and save (copy, then paste somewhere) often!


Everything on the right half of the screens below was done using this editor, meaning that printing each layout is just 2 tokens (although quite a few characters)! The demo print commands are given below, which you can load into the editor to inspect the different layers and see how some of the effects were done.

?"⁶jf4⁵ih⁶hᶜ2²2⁶xo⁶ye  \n  ⁶jg4⁴j⁶h²1⁶xm⁶yc  \n  ⁶jf4⁵ji⁶h⁶xn⁶yd  \n  ⁶jg5³h⁶h²2⁶xl⁶yb  \n  ⁶jg8⁵ijᶜa⁶x4⁶y6 2 tokens ⁶jl7³i only⁶jg7³iᶜ7⁶-#text:⁶jg5⁵ihany static⁶jff⁵iiᶜ0heavy outline⁶jff⁵ihheavy outline⁶jff³iheavy outline⁶jff³hheavy outline⁶jffheavy outline⁶jff⁴hheavy outline⁶jff⁴iheavy outline⁶jff⁵hiheavy outline⁶jff⁵hhᶜ7heavy outline⁶jfh⁵hiᶜ0light outline⁶jfh⁵ijlight outline⁶jfh⁴jlight outline⁶jfi³hlight outline⁶jfh⁵hjᶜ7light outline⁶jfk⁵jiᶜ0basic shadow⁶jfk⁵jhᶜ7basic shadow⁶jem⁵ijᶜ9multi-color (y)⁶jem⁵ijᶜa⁶y2multi-color (y)⁶jep⁵ihᶜ1²d⁶y6multi-color (x)⁶jep⁵ihᶜ2⁶-#⁶x2m u l t i - c o l o r   ( x )⁶jgsᶜ7²9⁶y8                       ⁶jfs⁵jh⁶x4⁶y6 containers \0"

--wysiwyg logo

--age of ants title screen
?"⁶jd7⁴i⁶w⁶tᶜ0age  ants\n⁶jf6⁴j⁶-w⁶-t⁶y7.         ⁶x3 .⁶x2     .⁶jt8⁵ji⁶x4⁶y6.⁶jc7⁵jh⁶w⁶tᶜ7age  ants\n⁶jt8⁵ih⁶-w⁶-t.⁶je6⁵ji⁶y7.         ⁶x3 .⁶x2     .⁶jj8³jᶜ0⁶x4⁶y6⁶:0060123515120800⁶jj7⁵jjᶜ7⁶:0060123515120800⁶jm6⁵ihᶜ5⁶x2.⁶x3 ⁶x2.⁶jc6⁵ih.⁶x3 ⁶x2.⁶jgc⁵jjᶜ0⁶x4difficulty:⁶jgc⁵jiᶜcdifficulty:⁶jdj⁵hjᶜ0press ❎ to start⁶jdj⁵hiᶜ9press ❎ to start⁶jqt⁵ihᶜ0v1.7⁶jqt³iᶜ6v1.7⁶jct³iᶜ0eeooty⁶jcs⁵ijᶜ6eeooty⁶jdm⁵hjᶜ0pause for options⁶jdm⁵hiᶜapause for options⁶jkf⁴hᶜ6\0"

Support for:

  • Various control code effects from the UI (tall, wide, stripey, inverted, border, x-width, y-height, fg color, optional bg color)
  • Text printed near the bottom of the screen (adds \0 to the end of the string so the console doesn't scroll)
  • Multi-line textboxes
  • As much character efficiency as sanely possible (will carry over as many effects as possible from textbox to textbox instead of resetting them each time)
  • Easy 𝘱𝘶𝘯𝘺𝘧𝘰𝘯𝘵 insertion
    • Note: pasting saved output containing punyfont into a pico-8 code editor won't retain punyfont, you will have to paste it into the p8 file from an external editor
  • Saving (to a 2-token print command) and loading via clipboard
  • Inline custom control codes via an "insert" menu
    • Useful for one-off effects on characters within a textbox, e.g. the gif above shows using wide mode for just one character, but can also be useful to make a space a littler bigger or smaller using x-width
    • Warning: This is an advanced feature! Subsequent text boxes may break if you don't "undo" the one-off effect (or not, test it yourself!)
    • Warning: Constructing invalid control code sequences (even as you edit) can have unexpected results - save before trying stuff!
P#128103 2023-04-04 23:26 ( Edited 2023-04-06 21:55)

:: Unfold ::

Hi! Think I ran into a bug when using the tall mode control code as part of a longer string (that then undoes it).

Seems like tall mode retains the doubled line height even after it's turned off in the same string. The following code produces this output:

?"\n ⁶t hi \n\n ⁶-t l1  \n  l2 \n"

...when I'd expect it to produce this ("l1 \n l2" is no longer in tall mode, so the height of the line break shouldn't be doubled):

It seems like the only way to revert to normal line height is to start a new print command, but that's another 2 tokens! This is affecting a WYSIWYG-style static text editor I'm planning on releasing.

P#128102 2023-04-04 02:33 ( Edited 2023-04-09 15:46)

:: Unfold ::

Age of Ants

send your worker ants to gather resources…
amass an army to defend your queen…
build your ant empire and conquer the lawn!

Age of Ants is a demake of Age of Empires II (with an ant/bug retheme, why not), featuring:

  • 1 map with 4 possible starting locations
  • Up to 2 allied AI opponents with 3 difficulty modes
  • 9 units, 8 buildings, 12 tech upgrades (most are repeatable)
  • Up to 99 active units per player
  • AoE2 controls & behaviors (let me know if something seems off, it's been a while)
  • Savefiles¹ (save in the pause menu to generate a screenshot, drag and drop to load)
  • Original soundtrack
  • wololoooo

Defeat the enemy queen to win!

It's playable on:

  • desktop with mouse (best experience, download here)
  • mobile/tablet (on bbs the page might scroll around - for a better experience try playing on itch)
  • handheld console

When the game first launches, open the pause menu and select the appropriate controls mode.

¹there are some caveats with savefiles, see the note on itch


I tried to stay as faithful to AoE2 controls as possible, but if you've never played or you need a refresher, check out the gifs below. If you prefer, there are also video versions embedded on itch (that way you can pause etc).

desktop tutorial (gif)

mobile tutorial (gif)

Note: Playing on mobile from BBS is a little broken, the page might scroll around as you drag onscreen. For a better experience try playing on itch.

handheld tutorial (gif)

advanced controls

The following controls try to copy some of AOE's quality-of-life shortcuts:

  • Double-click on a unit to select all visible units of that type
  • Left-click (or x) on a unit's portrait to deselect it
  • Right-click (or o) on a unit's portrait to select just it
  • Right-click when placing a building to build another one of the same type (replaces shift)
  • Workers automatically know how to allocate themselves to farms (e.g. with 5 workers selected, you can build 5 farms and they will all farm a different one)

tech tree


thank you to:

here's the uncompressed and highly commented sourcecode if anyone is curious. lots of token hacks and pretty tersely named variables, so hopefully the comments help demystify what's happening (i was originally attempting trying not to use shrinko but i caved once i added in helper text)

itch link for downloads and stuff: https://eeooty.itch.io/age-of-ants

Cart #age_of_ants-7 | 2023-03-24 | Code ▽ | Embed ▽ | License: CC4-BY-NC-SA

have fun! if you'd like, share savefiles of your games (they will still work even on the win/lose screen)


v1.7 (2023-03-24)

  • fix bug where units will stop short or not attack buildings behind fog of war
  • fix bug where game would crash shortly after loading game
  • fix bug where an enemy castle or tower could get undiscovered if it attacks a ladybug
  • fix fog borders appearing on far right edge of map even when those tiles are explored
  • enemy units attacking you from fog of war now get "discovered" (same behavior that buildings already had)
  • if you lose visibility of an enemy while chasing them, attacker will now continue to its last known location

v1.6 (2023-03-14)

  • fix bug where AI attackers could stall in the middle of the map
  • fix bug where AI sometimes didn't repair buildings
  • fix bug where a worker ant could drop resources off at an opponent mound (now it goes back to queen)
  • fix possible out of memory crash when there are too many active units
  • fix bug where mounds could take an extra long time to register as drop-offs
  • added sfx to difficulty selection in title screen

v1.5 (2023-03-11)

  • fix bug where HP would be very messed up (much lower) when loading a saved game
  • fix bug where unit spreading out wouldn't work on the right half of the map
  • fix bug where you could cancel an enemy building construction
  • fix bug where ladybug dying on an already-exhausted resource tile would produce a tile with infinite food
  • better army composition in endgame for hard ai
  • adjust damage multiplier table slightly
  • allow loading games on mobile via file upload button (itch.io only)
  • improve title screen font slightly

v1.4 (2023-02-28)

  • fix possible infinite loop when a large amount of units were near a building
  • fix bug where archers could walk through buildings when attacking
  • fix bug where if the game starts with the mouse was over where the minimap will be, the camera starts there
  • update "leaf" sprite (green resource) to have more contrast and more closely match the color of green resource value

v1.3 (2023-02-24)

  • new unit: spider web. a light wall built by spiders that can only be crossed by your own spiders
  • new: right-click when placing a building to build another one
  • new: small grass tiles now contain a small amount of plant (green) resource (should reduce confusion)
  • fixed: groups of overlapping units could go through buildings when spreading out (hopefully new algorithm for this is not too buggy/slow!)
  • fixed: when ladybug died, it could drop its food tile not at the closest available spot to it
  • fixed: tile directly above queen's right half was inaccessible
  • fixed: when easy AI started in the bottom-left corner it never built anything

v1.2 (2023-02-07)

  • regeneration tech upgrade now in mantis nest instead of castle
  • farm upgrade is now slightly cheaper
  • show victory if enemy queen is converted
  • make "move" (not "attack-move") the default action on mobile, remove support for attack-move on mobile
  • fixed bug where workers in a clump could move far from a resource and continue gathering
  • fixed bug where population count wouldn't transfer properly after a unit is converted

v1.1 (2023-02-05)

  • fixed bug where gathering workers would drop resources off all the way back to the queen unnecessarily
  • fixed bug where the bottom-right idle worker button would sometimes not detect idle workers (specifically if they depleted all nearby resources of a certain type)
  • fixed bug where double-clicking on a unit that overlapped with a building (e.g. farm) would not select all visible units of that type
  • fixed bug where worker ants would overlap when gathering resources
  • improved late-game AI resource allocation, plus normal and hard AIs now research fireball
  • updated map a little bit with a new mini pond tile
P#125247 2023-02-03 20:58 ( Edited 2023-03-25 03:43)

:: Unfold ::


sorry if this is "yet another" boring token-saving post (haven't been around long enough to know how common they are), but here's a list of (fairly) generalizable token saving tricks i've compiled while working on my upcoming game, in case it might be helpful to anyone. i think there are some on here that i haven't seen elsewhere on bbs, but also some of these may be pretty basic :)

this list does not include unpack(split()) tricks, or _ENV tricks, which can both have enormous token windfalls.

also, by far the best way to save tokens is not using these tricks, but to stare at your code hard... really hard... until something clicks and you realize you can convey 3 variables' worth of information with one, that you can reuse functions, that you're doing the same computation throughout your code many times and can use a variable, etc. or you know, to delete stuff.

these are kind of last-resort, please god just 3 more tokens sorts of things (although they do add up). hope they are of some use. with that aside, let's begin!

2 tokens: flr to \

this one is basic but it took me a while to learn about \. flr(a/b) can be written as a\b, saving 2 tokens. if flooring the result of a multiplication, assuming that one side is a constant, you can just invert: flr(a*2.5) to a\0.4

2 tokens: ceil to \ !?

this one is situational, but interestingly floor "decreases" a number even if it's negative, making it kind of function a bit like ceil in the opposite direction. the token save here only works if you can already negate the expression for free:


--to: (note += changes to -=)


of course you can use order of ops to fix any precedence issues this might cause (this time moving the negation to a constant in case there's no +=/-=):




1 token: calling apis with string params

since lua lets you call functions without parens if the only argument is a string/table literal, you can save a token on many standard api calls, e.g. sfx(5) to sfx"5", stat"", rnd"", btnp"", btn"", dget"", fillp"" (for this one, see the decimal value for your pattern in the console e.g. fillp"23130.5" for fillp(▒)), or even poke"" if you want to set that value to 0.

this will also work on your own functions too! if you call functions with a single truthy value, you can do fn"1" instead of fn(true), you can even do this if you call a function with a single numeric argument, but keep in mind this won't work if you do any comparisons with that argument. "3"+3 is allowed in lua, but not "3">3!

1 token: foreach

foreach(a, function(x) end) saves a token over for x in all(a) do. note that this means you can't break or return early, and it does come at the cost of performance (since you are introducing the overhead of function calls). it's not worth the token for nested loops / huge arrays!

1 token: next, inext

easy one:

for k,v in pairs(tbl) do


for k,v in next,tbl do

--ordered but only monotonically increasing past 1 are guaranteed:
for k,v in inext,tbl do

1 token: use return value of add/del/deli

if you can guarantee that the item being added/removed is present, you can replace a reference with the function:




1 token: hacking 'if' - add/del/deli

if you call add, del, or deli, the call will only "go through" if the first argument (the list) is a list. if it's falsey, it's a no-op. so you can save a token with:

if (x) add(arr, i)
add(x and arr,i)

note also that these functions are nil-forgiving for the second argument, too - add(arr,nil) does nothing, so you don't need if(x) add(arr,x)

1 token: hacking 'if' - camera/pal

if you want to call camera with x under condition c, save a token with:

if (c) camera(x)


camera(c and x)

note that this resets the camera if not c (which is actually sometimes what you want!)

the same applies to pal:

pal(c and unspl"5,6,7")

1 token: hacking 'if' - function call next to assignment

if you want to call any function under a condition, if there is assignment (or a function call that can take an extra argument) nearby, you can remove the if and save a token:

if (x) f()


k=v,x and f()

note that in this case the order matters! if it was the other way around:

if (x) f()

you can't make the same conversion if f() relies on the new value of k.

function call next to fully-loaded function call

note that this same trick works if you are calling another function nearby with all of its parameters passed:

if (x) f()


--pset doesn't take a 4th argument, but who cares!
pset(50,50,9,x and f())

same caveat applies here with ordering - f() will be called before pset()

essentially we are forcing lua to have a floating expression without an assignment.

not as or

in all of the 'hacking if' cases above, if your condition x is of the form not y, you can usually do or y instead of and not y!

1-3 tokens: hacking 'if' - function calls in ternary

finally, this is kind of specific, but let's say you have an expression that should go one way under one condition, and another way otherwise. but let's also say that in one of those cases, you also need to make a function call.

if the call returns truthy, it can be prefixed with and to the value associated with the condition under which you want it to run:

---- pset returns truthy, so prefix with `and`
----  -3 tokens (-2 if on "truthy" side of ternary)

a=c and x or y
if(not c) pset(3,3,5)


a=c and x or pset(3,3,5) and y

if the call returns falsey, it can be prefixed with or, but you'll have to add parens:

---- sfx returns falsey, so prefix with `or` and add parens
----  -1 token (-2 if on "falsey" side of ternary)

func(c and x or y)
if(c) sfx"3"


func(c and (sfx"3" or x) or y)

1 token: max to coerce to num

you can use max() to coerce a potentially non-number value into 0: max(nil)=>0; max(false)=>0; max(5)=>5. this saves a token over x or 0 because if you are doing math you will pay an extra token for the parens:

5+(x or 0)

it does have limits: be aware that max(true)=>0, and max("5")=>5

2 tokens per argument: local functions

if a function is only used by one other function, you can define it inside the other (locally) and remove any arguments already in scope:

function g(a,x)
function f()
 local a,b,c=...


function f()
 local a,b,c=...
 local function g(x)

1 token: table length checks

you can replace the expression #tbl>x with tbl[x+1] (which saves a token if x is a literal), or the expression #tbl>=x with tbl[x] (which saves a token):

if (#tbl>0) do_x()
if (#tbl>=threshold) do_y()


if (tbl[1]) do_x()
if (tbl[threshold]) do_y()

note: this trick won't work if the table can contain false!

??? tokens: practical bit ops

sometimes bit ops can be useful to save a bunch of tokens. pardon the somewhat specific examples, hopefully these get you thinking:

x!=0 and y!=0 to x&y!=0 (-2 tokens)
x==0 and y==0 to x|y==0 (-2 tokens)

(these also have the advantage of not using a boolean operator, so if you invert one of these (e.g. x|y!=0 instead of x!=0 or y!=0), you won't need parens if you and it with something)

x^^=0xf0: if x starts at 0, this toggles between 0 and a high number. if passed into e.g. camera(x), can be used to toggle showing/hiding something

0!=~0, so if you want to toggle something, say a controls option that you want to store in cartdata, you can do it pretty tersely with the binary not operator ~:

menuitem(1,"toggle foo",function() dset(0,~dget"0") end)
if dget"1"==0 then
 --default, since cartdata defaults to 0

1 token: pal color 1

another highly specific one, but if you're using pal on color 1, save a token with pal(1,x) to pal{x}

1 token: mid for range-checking

mid() can sometimes save a token when doing a range check of the form x >= 0 and x < y (or negated):

x>=0 and x<8192

one extra edge case with this not involving 0 that i thought was cool:

dt<.1 or dt>.25

--wait it's the same number of tokens...
--but the first one has an `or`, so if it needs
--to be combined with `and`, it'll need parens!

(context here is flashing a selection once, dt is the time since the selection)

2 tokens: do you really need cls()?

it's common to add cls() when starting a new project, but if you fill the whole screen each frame with e.g. map() you might not need it!

??? tokens: control codes

a LOT can be done with control codes, this is a large topic. but in general, i've found that if you are printing to the screen you generally don't ever need to pass (constant) args to print (that is, ?) since you can move the cursor with \^j and \^+, and set color with \f.

moreover, if you are printing a bunch of hard-coded text, e.g. a title screen, you generally only need a single call to print (?) since you can jump around on the screen so easily. need a shadow? first print your text in a dark color, then use \^j or \+ to jump backwards and slightly up and re-print your text in the foreground color.

i've spent so much time tweaking horrible controlcode-ridden strings, i kind of want to write a utility cart that is a kind of WYSIWYG editor for text that will spit out a single horrible string to print. EDIT: i've done this!

1 token: return value of ?

this one is a little hacky, but since ? is an alias for print that can be called without parens, you can still capture its return value if you need to get the max x-drawn. just make sure you add a newline after the last argument to ?:

-- line x1 will be max_x-3, the -3 has to be on the next line


1 token: .. operator with numbers

the .. string concat operator can be weird with numbers. if it's placed directly after a number, the parser will complain, expecting a fractional component following the first .. but if you add a space...:

?"minutes: "..t\60.." seconds: "..t%60
--malformed number near '60..'!

--i thought this meant that you needed parens:
?"minutes: "..(t\60).." seconds: "..t%60

--but you can save that token if you just add a space!
?"minutes: "..t\60 .." seconds: "..t%60
P#125136 2023-02-01 21:50 ( Edited 2023-04-09 17:12)

Follow Lexaloffle:          
Generated 2023-05-29 04:58:58 | 0.107s | Q:21