Log In  
[back to top]


I know a lot of people, myself included, usually write their pico~8 code a little off the cuff tinkering with it until it works. Which tends to be more fun in my experience. But it can also be incredibly frustrating if I'm working on sometime more complex where every time I change something I break something else. And in those cases planning out some formal tests helps me maintain my sanity and get the thing actually working much faster than I probably would otherwise. And since I'm working on something fairly complex at the moment, I took a bit of a detour and put together a little test framework and thought I'd make it available for anybody else who might find it useful.

The code is on github: https://github.com/jasondelaat/pico8-tools/tree/release/testo-8
Or you can just copy it here:

--------------------------------
-- testo-8: testing framework
-- copyright (c) 2022 jason delaat
-- mit license: https://github.com/jasondelaat/pico8-tools/blob/release/license
--------------------------------
do
   local all_tests = {}

   local function smt(t, mt)
      return setmetatable(t, {__index=mt})
   end

   local function shallow_copy(lst)
      local copy = {}
      for l in all(lst) do
         add(copy, l)
      end
      return copy
   end

   local function filter(f, lst)
      local results = {}
      for l in all(lst) do
         if f(l) then
            add(results, l)
         end
      end
      return results
   end

   local execute_meta = {
      execute=function(self)
         local result = self[4](self[3](self[2]()))
         if self._cleanup[1] then
            self._cleanup[1]()
         end
         return {
            result,
            self[1]..self.when_txt..self.result_txt
         }
      end
   }

   local when_result_meta
   local result_meta = {
      result=function(self, txt, fn)
         local t = shallow_copy(self)
         t.when_txt = self.when_txt
         t.result_txt = 'result '..txt..'\n'
         t._cleanup = self._cleanup
         add(t, fn)
         add(all_tests, smt(t, execute_meta))
         return smt(self, when_result_meta)
      end
   }

   local _cleanup
   local when_meta = {
      when=function(self, txt, fn)
         _cleanup = {}
         local t = shallow_copy(self)
         t.when_txt = 'when '..txt..'\n'
         t[3] = fn
         t._cleanup = _cleanup
         return smt(t, result_meta)
      end
   }

   when_result_meta = {
      when=when_meta.when,
      result=result_meta.result,
      cleanup=function(self, f)
         add(_cleanup, f)
         return self
      end
   }

   local given_meta = {
      given=function(self, txt, fn)
         local msg = self[1]..'given '..txt..'\n'
         return smt({msg, fn}, when_meta)
      end
   }
   function test(name)
      _cleanup = {}
      local t = smt({name..':\n', _cleanup=_cleanup}, given_meta)
      return t
   end

   local function run_tests()
      cls()
      cursor(0, 7)
      local results = {}
      for t in all(all_tests) do
         add(results, t:execute())
      end
      local failures =
         results and filter(function(r) return not r[1] end, results) or 0
      if #failures == 0 then
         print('all '..#all_tests..' tests passed!', 0, 0, 11)
      else
         for f in all(failures) do
            print(f[2])
         end
         rectfill(0, 0, 127, 6, 0)
         print(#failures..'/'..#all_tests..' tests failed:\n', 0, 0, 8)
         cursor(0, 127)
      end
   end

   function _init()
      run_tests()
   end
end
-- end testo-8 ------------------------------

[ Continue Reading.. ]

1
0 comments



Cart #jd_dice_wip-0 | 2022-10-16 | Code ▽ | Embed ▽ | No License

So I've started working on this. It's still very early and I've only got some of the UI elements and the very beginnings of procedural generation in place but I think there's enough to make it worth sharing.

Controls

While moving around:

  • 🅾️ to switch to the ability selector. (Coloured boxed bottom-middle)

In ability selector:

  • 🅾️ to select highlighted item
  • ❎ to cancel

Choosing 'END TURN' pulls up a 'Dice Selector' menu which doesn't do anything yet.
Press either ❎ or 🅾️ to dismiss it.

Notes

  • Actual movement in the game will be tile based but I wanted people to be able to zoom around the big empty levels fairly quickly for now because it's not like there's much to see.
  • At the moment the levels are just a big loop of straight, boring grey hallways surrounded by blackness but eventually room will be able to expand into the available empty spaces, there will be locks, keys, enemies, puzzles, etc.
  • The mini-map may or may not be in the actual game but I wanted to give an overall idea of the shape of the generated levels.
  • The green square in-level is the level entrance and the red one the level exit. Nothing happens yet when you get to them.
  • The stats on the bottom left are hard-coded and don't do/mean anything yet.

I'm trying my hand at procedural generation using graph re-writing. There's a fairly straight-forward summary of the process here for anyone interested: https://www.boristhebrave.com/2021/04/02/graph-rewriting/

This version isn't actually using re-writing yet which is why all the interesting stuff is missing from the level. It's just a graph with a basic loop. I've got the system written and—I think—working as expected so next steps are to start testing some rules for slightly more interesting levels to shake out all the bugs and then take it from there. Oh yeah, and all the actual game mechanics. Those will probably help too.

3 comments



So maybe this is old news to everybody but me but I just discovered it by accident. Turns out you can define local functions! It makes sense but it somehow never occurred to me to try before.

Here's a quick example:

do
   local function a()
      print('in a')
   end

   function b()
      print('in b: calling a')
      a()
   end

   print('in local scope')
   print(a)
   print(b)
   a()
   b()
   print('leaving local scope\n\n')
end

print('in global scope')
print(a)
print(b)
b()

Output:

in local scope
[function]
[function]
in a
in b: calling a
in a
leaving local scope

in global scope
[nil]
[function]
in b: calling a
in a

That's it. Just thought it was interesting.

1
5 comments



I had a bit of time to tinker yesterday. I'm not sure what made me think of the old Infocom text adventures but here we are.

If you're familiar with Text Adventures (or Interactive Fiction) you may know that one of—maybe the—most popular tool for creating them has been the Inform language. The current version is Inform 7 but waaaaay back when I was first learning, it is was Inform 6.

Anyway, I decided to throw together a quick little IF authoring API loosely based on Inform 6. It is by no means complete or particularly advanced. Basically, I followed the first tutorial for the game Heidi from The Inform Beginner's Guide and implemented just enough to make it work. But work it does! Mostly. I think...

[ Continue Reading.. ]

8
11 comments



[sfx]

Made for the midilib custom SFX instrument project

And in action:

Cart #jdmidi_122_seashore-0 | 2022-09-13 | Code ▽ | Embed ▽ | License: CC4-BY-NC-SA
2

The animation is taken from one of my other projects. The palm tree image is adapted from the tutorial The basics of Painting with Maths by Inigo Quilez.
2
3 comments



I mean, who doesn't compose for helicopter, right?

Made for the midilib custom SFX instrument project

Credit's always nice but not required.

[sfx]

Here it is in action. Please forgive the poorly and hastily drawn helicopter.

Cart #jdmidi_125_helicopter-1 | 2022-09-13 | Code ▽ | Embed ▽ | License: CC4-BY-NC-SA
3

3
4 comments



So this isn't strictly Pico-8 related but I just came across a video series on Youtube which I thought others here might enjoy.

The Animation of Final Fantasy

The intention seems to be to follow the evolution of FF animation all the way from FF-I up to FF-whatever-number-they're-up-to-by-the-time-he-finishes-the-series.

So far he's only up to FF-IV (the first Super Famicon/SNES release) but I think the first three (the Famicon/NES releases) are probably most interesting to Pico-8 developers. I thought it was an interesting look into how the developers managed with so few assets and—especially in FF-II—how they were able to use those assets to tell a story and convey at least some very basic sense of character.

0 comments



PICO Composer v0.3

Changes


2022-06-15:

  • Set default tempo to 120 bpm
  • Set default time signature to 4/4
  • Implemented exporter and importer code

2022-06-06:

  • Fixed display code which wasn't properly accounting for sharps causing notes to eventually be drawn offscreen.

Cart #picomposer-5 | 2022-06-15 | Code ▽ | Embed ▽ | No License
7

PICO Composer main menu

When you start the cart you'll be prompted to load an album or create a new one. Use the up/down arrow keys to navigate the menu and enter to select. Either, or both, of the tempo and time signature can be left blank and you'll be given the defaults of 120 bpm and a 4/4 time signature. Any time signature that can't be parsed properly will default to 4/4.

[ Continue Reading.. ]

7
10 comments



I may have a non-standard definition of fun...

Disclaimer: I'm fully aware that this is quite likely a completely useless little utility as far as PICO-8 development is concerned. I just like writing this kind of thing just for fun. But if you do find it interesting or useful, let me know!

This is basically just a function which allows you to create curried functions—which I attempt to explain below if you're unfamiliar with them—which can be partially applied in a natural way in Lua. I'm not sure why it took me so long to write this since it's just a rip-off of my python implementation of the same thing.

Anyway, here it is on github or you can copy/paste the code from the example.

Example

Here's a simple little proto-game thing which I stuffed full of as many curried functions as I thought I could reasonably get away with. Not gonna claim that it's the best—or even good—way to organize a game but I think it does an okay job of showing how to use partial application as a sort of dependency injection/data encapsulation.

[ Continue Reading.. ]

2
3 comments



Just creating a thread to collect music that I write. Mostly just for myself—to have things I like all in one place which I'll update periodically as I write new stuff—but feedback is always welcome should anyone feel so inclined. I'm mostly not making any efforts to use space efficiently. I probably should. Meh, one day. Also, I'm terrible at naming things so if anybody feels like suggesting titles or some kind of coherent naming scheme, I'm all ears.

With that in mind here's my first entry. A jaunty little tune I threw together this morning.

2022-04-16:

Song 1: Jaunty Tune in Eb Major
[sfx]

This one was from a few weeks back. Don't remember when exactly. I was going for something sort of mysterious-ish but still with some upbeat parts. Maybe a bit more repetitive than I'd like but I think turned out alright overall. C Minor, I think.

[ Continue Reading.. ]

4
1 comment



[sfx]

I've not had much time lately but thought I'd throw up a WIP that I've been playing with for a few days as a bit of a distraction from...everything. It's one of the longer pieces I've done so far at just a bit under three minutes and probably one of the first that feels like a whole piece rather than just a section or an experiment.

There's a couple rather clunky key changes which stick out like sore thumbs. Still figuring those out. Sometimes they work for me and sometimes they don't and I haven't quite figured out how to turn one that's not working into one that does. But I'm getting there. So far I'm only using 3 of the four channels so I'll probably add some percussion-y and/or other decorations in on that fourth channel at some point. Which might actually help with the key changes? Maybe? Don't know.

[ Continue Reading.. ]

5
3 comments



I'm brand new to making music and I'm interested in hearing about how different people approach composing for their games. Not in terms of using the music editor—I'm fine with that—but more about your process/workflow for creating the music itself. You can assume that I understand about scales/modes, keys, chords, chord progressions, etc. and feel free to sling around jargon/technical terms if you want I can always go look up anything I don't understand.

I realize that everybody goes about things their own way; I'm not looking for a "right" way to make music, more just interested in peeking into people's brains while I try to figure out my own way of doing it.

The kinds of things I'm interested in (not an exhaustive list!):

  1. How do you start? With the main melody? With a beat? Chords? Something else? Do you always start the same way or does it depend? On what?
  2. Do you create your music from scratch directly in the PICO-8 music editor or do you use something else, like a DAW and then "translate" the music over into PICO-8?

[ Continue Reading.. ]

12
23 comments



I didn't know much about compression algorithms when I started looking into this stuff and, in truth, I still know very little. So this may all be old-hat to a lot of people but hopefully somebody finds it interesting.

What am I compressing and why do I want to compress it?

For the last month or so I've been playing around with Signed Distance Fields (SDFs) first by making some simple pictures with them and using them to handle collsiion detection and then using them as the basis of a procedural morphing animation.

I'm interested in SDFs for a few reasons: I'm not much of an artist but I do like math. If I can substitute math for art in certain situations, that potentially works to my benefit. Also, since SDFs can be used to handle collision detection and they can be updated on the fly with boolean-like operators—union, intersection and difference—they seem like they could be a good choice for modeling level geomoetry and, in particular, destructible level geometry. But mostly I just like playing with them.

[ Continue Reading.. ]

3
31 comments



Cart #demo_realtime_sdf-0 | 2021-10-23 | Code ▽ | Embed ▽ | License: CC4-BY-NC-SA
12

The final image is an adaptation of the image from the Principles of painting with math tutorial by Inigo Quilez.

I'm using fixed time-steps and the animation is intentionally a bit slow so "real time" might be fudging the truth a bit but I thought I wouldn't be able to animate these things at all and it turns out I was wrong. I just needed to trade off time for space in time honoured fashion. That's why there's a "loading screen" at the start: most of the time intensive calculations are being done up front. I used the SDF utility I posted the other day—not strictly necessary of course, you can just write the functions directly—to create all the signed distance functions used for the animations and then converted them to arrays by pre-calculating all the distances and turning distance checks into a simple table lookup.

[ Continue Reading.. ]

12
3 comments



PICO-8 has been my obsession for the last few months but math is my always obsession and lately I've been having a lot of fun playing around with various aspects of geometry so when I hit a weird bug in my other project I decided to take a break and shift gears a bit. So I threw together a little interface for working with 2D signed distance fields/functions.

It's fairly hefty at a little over 500 tokens so probably not super useful generally speaking but pretty fun to play with, if I do say so myself.

Here are a couple series of images I've created so far. In each, all of the images are made from the same SDF and just messing with how colours are assigned based on the distance.

That last one is my attempt at a ripply/watery reflection which, I think, didn't come out too badly.

And some hearts.

[ Continue Reading.. ]

1
1 comment



Building on my previous ray casting toy I've been playing around with shadow casting.

Cart #difepoduko-1 | 2021-10-10 | Code ▽ | Embed ▽ | License: CC4-BY-NC-SA
6

Old (30fps):

New (60fps):

Most examples of this I've come across work with tiles/on a grid. This works with arbitrary line segments with the following caveat: the more line segments, the worse the performance. This example runs okay at 30fps but not so much at 60fps.

Most of my time seems to be lost in the actual casting of rays.

Things I plan to do but haven't done yet:

  1. Currently I'm casting two rays, slightly offset, towards every line segment's end-points (after removing duplicates so I'm not casting at the same point more than once.) I figure it shouldn't be too terribly difficult to modify it so a ray which hits a line's end-point is extended to the next wall instead of stopping. Then I'd only need one ray per end-point cutting my total number of rays in half.

[ Continue Reading.. ]

6
1 comment



Had a video on ray casting pass by in my Youtube feed the other day and thought I'd play around with it a bit.

There are the four outer walls and then four randomly generated interior walls. It's casting 27 rays so, when moving, that's 216 intersections calculated per frame at 60 fps. Either more walls or more rays forces it down to 30 fps so it's a bit of a performance hog (okay, a huge performance hog.) I'm sure it could be optimized somewhat, though I'm unlikely to do it.

Edit: Okay, so apparently I lied. I realized that I could have my rays do double duty by calculating two intersection points each, one in each direction along the line. So I get effectively twice as many rays for virtually no extra cost.

Cart #yirijahiwe-1 | 2021-09-27 | Code ▽ | Embed ▽ | License: CC4-BY-NC-SA
3

[ Continue Reading.. ]

3
3 comments



State Machines

A wrote (a couple variations of) a simple state machine manager. The code is on github under an MIT license. There are a couple demos down at the bottom of the page.

  • state-machines-du (107 Tokens): each state has its own draw and
    update methods
  • state-machines-st (111 Tokens): each state has setup and teardown
    methods which are run only when the state is entered and exited
    respectively

Usage

Creating a state machine

To create a new state machine use the 'new' method:

    sm = state_machine:new()

Adding states

After creating the machine itself you need to add states. A state
consists of four things:

  • A unique name or identifier: Most likely a string but can be
    anything as long as it's unique.
  • A transition function: This function is called once per cycle
    and should return the identifier of the state the machine
    should switch to.
  • An update (or setup) function: The update function is called
    once per cycle and should update variables, etc. associated
    with the state. For state-machine-st.lua, this is instead a
    setup function which is only run once each time the machine
    enters this state.
  • A draw (or teardown) function: The draw function is called
    once per cycle and should draw everything relevant to the
    state. For state-machine-st.lua, this is instead a teardown
    function which is only run once each time the machine exits
    this state.

Add a state to the machine using the 'add_state' method:

    sm:add_state(
       -- identifier
       'a state',

       -- transition function
       function()
          if btnp(5) then
    	 return 'some other state'
          else
    	 return 'a state'
          end
       end,

       -- update function
       function()
          if timer then
    	 timer += 1
          else
    	 timer = 0
          end
       end,

       -- draw function
       function()
          print(timer)
       end
    )

Using the state machine

  1. state-machine-du.lua

    Once you've created a state machine and added some states using it
    is simple: Set the initial state then call the update and draw
    methods.

        function _init()
           sm:set_state('a state')
        end

        function _update()
           sm:update()
        end

        function _draw()
           cls()
           sm:draw()
        end

The update method calls the current state's transition function
and changes the current state if necessary and then calls the
current state's update function. The draw method calls the current
state's draw function.

  1. state-machine-st.lua

    The setup/teardown version is basically the same except there is
    no draw method and the update method does a bit more work, so all
    you need is this:

        function _init()
           sm:set_state('state 1')
        end

        function _update()
           sm:update()
           -- whatever other update stuff you need to do.
        end

        function _draw()
           cls()
           -- whatever draw stuff you need to do.
        end

The update method in this version also calls the transition
function. If a state change is necessary, then the current state's
teardown function is called, then the current state is changed,
and finally the new state's setup function is called.

Demos

basic-sm

A very basic state machine with two states. Press X/V to switch from state 1 to state 2, Z/C to switch from state 2 to state 1.

[ Continue Reading.. ]

2 comments



I've finally started working on an actual game. The programming is not a problem but art, on the other hand, well...there be dragons.

I'm not an artist and it doesn't come naturally to me but pixel art, 8x8 pixel art in particular, seems much more approachable than more traditional types of art. I think because the feedback cycle is shorter and more obvious: with only 64 pixels to play with, it's pretty obvious when you put one in the wrong spot and there are a limited number of choices available for how to fix the problem. So I spent a few days poking around the sprite editor doing a bit of an "art study" to see what I could figure out. Comments, tips, tricks, etc. very much welcome.

This is very much not a tutorial. I'm not and artist and I'm not qualified to teach anybody anything about doing art of any kind. If people find it interesting or useful, great! But mostly I'm just using this as a place to record observations for future me to think about when I'm trying to figure out what the heck my game should actually look like. I'll add the full sprite sheet at the bottom if anyone's interested in having a look at it.

[ Continue Reading.. ]

7
2 comments



I hate fiddling with meaningless magic numbers trying to get a behaviour right. I prefer a bunch of knobs with predictable effects. So I made this little tool for fine tuning variable-height jumping behaviour for platformers.

Note: As written, it's possible for the cart to get stuck in an infinite loop for certain values so read below on how to, hopefully, avoid that issue.

Cart #jump_tuner_1-0 | 2021-06-25 | Code ▽ | Embed ▽ | No License
7

Usage:

  • Up/Down to select a value
  • Left/Right to modify the value
  • X to test the jump, tap for small jumps, hold for higher jumps

There are three tunable values and a fourth value which is calculated for you and can't be changed manually. Once you get something you're happy with, note down the values, and use or modify the jumping code from this cart in your own project. I think I've clearly indicated all the parts of the code that can be deleted and which parts should be modified. If you do use it, attribution is appreciated but not necessary.

[ Continue Reading.. ]

7
0 comments





Top    Load More Posts ->