Log In  

Guide your character on a journey through dungeons inhabited by perilous adversaries. Solve puzzles and discover secrets to get your hands on that special kind of loot.

The game pays homage to the old isometric classics like Cadaver, Hero Quest, Darkmere etc.



  • Add monsters, with basic AI (wander/patrol and attack on sight)
  • Combat and damage formulas
  • If token/compressed space allows it I'll add the map feature, else it'll be kicked to the curb.
  • Player character animations (idle, walk, melee, cast spell, die)
  • Add some more props/decor if spritesheet allows it
  • Optimize
  • Optimize
  • Optimize
  • ...Optimize!

Cart #55555 | 2018-08-23 | Code ▽ | Embed ▽ | No License


  • P/ENTER = Bring up menu (Inventory, Stats, Map)
  • Arrow keys = Move
  • X = Interact in the world / Select in menus
  • Z = Exit from menus

The source code is not yet optimized and I apologize for the messy code. :)

If you have suggestions on how to improve parts of the source code feel free to comment about it. I would appreciate any input regarding optimizations for speed, token/compression limits and so forth.

Short backstory
A couple weeks ago I created a pixel art mockup of an imaginary isometric rpg for PICO-8. Although rough and unfinished I got a lot of positive feedback.

Later I decided to download and install PICO-8 for the first time just to have a look at it. Started reading up on the API and really liked what I saw. First I made a quick ISO floor render test to see if it could be done and if it would look alright. One thing led to another and a week later the result is what you see here.


[UPDATE 0.6.4]

  • Token optimizations
  • Added sound when opening doors and containers.

[UPDATE 0.6.3]

  • Containers now drops items.
  • Spells can be cast when holding a spellbook.
  • Added road/path tiles.
  • Made denser forest.
  • Change subterrain color from yellow/gold to be more stone colored. I think that looks better.

[UPDATE 0.6.2]

  • Added support for mixing of tilesets per area (Before was only one tileset per area/room). This allows for more interesting areas.

[UPDATE 0.6.1]

  • No longer storing data in delimited strings but instead using JSON, and a very nice parser, courtesy of freds72
  • Many optimizations and code cleanups. Reduced token count with ~500 despite adding functionality.
  • Ingame messages/infostrings are now managed using the same editor as the rest.

Previous versions:

Cart #55499 | 2018-08-22 | Code ▽ | Embed ▽ | No License

Cart #55448 | 2018-08-21 | Code ▽ | Embed ▽ | No License

Cart #55335 | 2018-08-17 | Code ▽ | Embed ▽ | No License

Cart #55273 | 2018-08-16 | Code ▽ | Embed ▽ | No License

P#55282 2018-08-16 20:06 ( Edited 2018-08-17 00:06)

There is a strong and steady supply of people out there who do not like Isolinear games. Not necessarily for the view but the movements of their player in it.

Suggest you have as it is and add the option that UP, DOWN, LEFT, and RIGHT truly do move the player in that absolute direction despite the isolinear view.

Other than that, your game looks very well rendered and certainly worthy of being explored to conclusion.

P#55285 2018-08-16 20:32 ( Edited 2018-08-17 00:33)

Thanks for the feedback :)

It did not even occur to me that movement could be anything else than it is now. Perhaps my mind is too accustomed to isometric games and their traditional movement/controls. I do however agree that I should add an option for that :) Thanks again.

P#55286 2018-08-16 20:39 ( Edited 2018-08-17 00:39)

Good to see this cart moving from Twitter to the BBS!

Good news is: a lot of tokens can be salvaged!

  • inline initialization of tables:
— no
local obj={}
— yes
local obj={
  • group initialization:
    local a,b,c=1,2,3

Overall the game could benefit from my json parser (forum is a bit broken right now but look for the castle dracual/frankeinsten game) or my previous games. More on that when I am back from vacations!

Other code comments:

  • you should really store items per room, that would make render/updates independant of the total size of the ‘castle’
  • scope your variables! All local variables should be prefixed by ‘local’ (ahah). Added bonus: local variables require less cpu cycles for operations
  • use boolean value directly:
  • do you know pico as a ‘tostr’ function?
  • avoid for/all (slow) in favor of for/pairs
P#55306 2018-08-17 03:19 ( Edited 2018-08-17 07:36)

Hey freds72. I appreciate the tips as I have to start salvage tokens. Limits are fast approaching :D

I'll convert to inline init of tables right this second and group init of local vars and see how many tokens can be shaved off.

I thought all my local vars were prefixed :) At least that was the intention. Must've missed some!

As for "items per room" I do just that in the init_room(). Every time player enters a new room arrays are filled with the content of that room (actor[] and draw_queue[]). I do have a plan to add everything to a single draw_queue array at some point. That would perhaps shave off some tokens but also reduce cpu usage.

I was not aware of tostr function no :D Thanks!

P#55307 2018-08-17 03:45 ( Edited 2018-08-17 07:45)

wow! that's one the best looking games around here! and it seems the gameplay will be on par!

I don't mind the diagonal movement at all, but I tend to tilt the keyboard according to it and it's a little awkward on the right. maybe an option to turn it on the left? or like dw suggested, just make the movements straight, since you aren't bound to tiles.

I'm curious about the map, since the room disposition doesn't seem "euclidian" :)

P#55311 2018-08-17 04:56 ( Edited 2018-08-17 08:56)

freds72: After inlining table inits and grouping vars this is the result.

-326 tokens
-2314 chars
-260 compressed

That's not bad. Everything counts :) Thank you again.

Downside is of course that it's now much less readable :D

P#55312 2018-08-17 05:10 ( Edited 2018-08-17 09:10)

ultrabrite: Thank you! I'm happy you like it :D

Yes I'll be adding a settings page eventually where I'll put a toggle for the controls.

About the map. I hadn't given it much thought until now, I just knew I wanted to have a map :D At this point the current rooms aren't really suitable for a grid map but these are just temporary rooms while developing. I will add rooms with more thought later on.

Visually I'm thinking something like this:

The map feature is not a critical one and if I run into severe token problems then the map is out :D

P#55314 2018-08-17 05:34 ( Edited 2018-08-17 09:35)

freds72: I tried your json parser and it resulted in another ~350 tokens off, and it makes life a lot easier dealing with json instead of parsing delimited strings.

So if you don't mind then I'll be using that json parser. You'll be credited of course :)

P#55318 2018-08-17 08:36 ( Edited 2018-08-17 12:36)

I don’t mind - I just take 20% of revenues ;)

I am also a big fan of json flexibility and ease of editing.

Note that parser can automatically bind functions defined in the global _g table.
See that discussion: https://www.lexaloffle.com/bbs/?tid=31457

The whole ‘load_room’ function is likely to go away!
I am usually coupling the parser with a ‘clone’ function that can understand rnd values.

P#55333 2018-08-17 13:18 ( Edited 2018-08-17 17:18)

Haha :)

That discussion was a nice read and an eyeopener. Might track back to that later on when all mechanics are in place. I'm not experienced enough to do it correctly the first time. I need a bunch of iterations before it's "production quality".

I've optimized and cleaned up a bit now. Looks much better now with the json's.

Can you elaborate what you mean by "with a 'clone' function that can understand rnd values"?

P#55334 2018-08-17 14:02 ( Edited 2018-08-17 18:02)

I use json to describe ‘templates’ - in order to create a unique instance, I use a copy function:

function clone(src,dst)
    -- safety checks
    if(src==dst) assert()
    if(type(src)!="table") assert()
    dst=dst or {}
    for k,v in pairs(src) do
        if(not dst[k]) dst[k]=v
    -- randomize selected values
    if src.rnd then
        for k,v in pairs(src.rnd) do
            -- don't overwrite values
            if not dst[k] then
                dst[k]=v[3] and rndarray(v) or rndlerp(v[1],v[2])
    return dst

local actors={}
local all_actors=json_parse’{« npc »:{« rnd »:{« hp »:[3,4]}}}’
— usage
function make_npc(x,y)
local a=clone(all_actors[« npc »],{
 return a
— somewhere in game
local a_npc=make_npc(12,4)
— result: a npc with a level between 3 and 4, placed at coords 12/4

The trick is that the copy function recognizes some field names (like rnd) and will call the appropriate function.

P#55336 2018-08-17 14:58 ( Edited 2018-08-17 19:00)

Ahh, nice. I see why that would be useful. I might look into implementing sometime along those lines when I start adding monsters and npcs.

P#55342 2018-08-17 16:40 ( Edited 2018-08-17 20:40)

Just a fun tip for setting controls since there has been some mention of it. Firstly, yes I believe there should be an option to just change them. Possibly an option for just rotation of which key is "up." and maybe an option for left and right is rotate, foward and back is move foward or back? But, I didn't come here to post that, I came here to post an interesting game design choice you could make to avoid having players need to go set up their controls.

Start off the game with a "Wizard/Spirit Animal/Ghost/Whatever sort of Guide you want" who greets you with a message like "Welcome to the dungeon! It's dangerous and you're probably going to die! Please head down this hall into the next room where we will get you equipped!" Then you can just have the starting room be a simple hallway up to a door. Then just wait for key input and depending on what key the player presses first, that's the one you assign as up, or whatever direction the hallway is facing, and you set the others accordingly. This way, the player is ALWAYS right, with no need to get set controls in the menu.

P#55365 2018-08-18 07:50 ( Edited 2018-08-18 11:54)

Thanks for the input Cabledragon. I will consider it. Personally I prefer the controls as is and I might just go with that, as that feels (imho) like the most correct way. Controls and directions is relative to the iso view perspective. North is not up towards the top of the screen but along the diagonal line towards top-right. Then it makes more sense to me that if you press UP it will go north.

P#55378 2018-08-19 06:19 ( Edited 2018-08-19 10:19)

The problem with that zoop is that just as many people will see that same northern diagonal line, but towards the top-left. It's kinda the same with like reading things, how a good chunk of the world reads things left to right yet still others read it right to left. People's brains will always want to process it how they prefer. I'm not arguing for or against additional controls, I should add. I think it should exist, but a design choice is a design choice, that's up to you. I personally have no issues with the controls as they are.

P#55445 2018-08-21 06:31 ( Edited 2018-08-21 10:31)

Cabledragon: Ahh, I misunderstood. I thought you mean that if you press UP the character should walk upwards vertically not diagonally. I understand now. North could very well be diagonally left, not just diagonally right. I will add option for this :) Maybe it's because I'm left handed that I automatically think of north to the diagonally right. Thanks for the input :)

P#55447 2018-08-21 07:35 ( Edited 2018-08-21 11:42)

I think this looks awesome

P#55450 2018-08-21 08:17 ( Edited 2018-08-21 12:17)

Thank you dollarone :) I am happy with how it looks now. I just have to finish up the last few features and start making a game out of it.

P#55454 2018-08-21 08:49 ( Edited 2018-08-21 12:49)

Here's a token-saving tip...

These are equivalent:

table.member = x
table["member"] = x

The first is just syntactic sugar for the second, to allow Lua to seem like its tables are objects with members.

Thus, you can rewrite this code:

            for j=1,#inventory.items[i].item.mods do
                local mod=inventory.items[i].item.mods[j]
                if (mod.name=="att") player.att+=mod.value
                if (mod.name=="def") player.def+=mod.value
                if (mod.name=="mind") player.mind+=mod.value
                if (mod.name=="body") player.body+=mod.value

Like this:

            for dummy,mod in pairs(inventory.items[i].item.mods) do

Note that, since the order isn't important, I've also switched to using the unordered key/value pairs() iterator, which gives you the mod=mods[j] part for free.

This would probably read more clearly if it were mod.attrname or just mod.attr, but you get the idea.

P#55512 2018-08-22 15:47 ( Edited 2018-08-22 20:05)

Oooh, nice one Felice. Thank you so much. That saves a couple tokens per use (and also some chars/compression). Everything helps.

However, this one adds one token:




But overall I save more by using your tip.

Got any more tips? :D

P#55529 2018-08-23 02:14 ( Edited 2018-08-23 06:21)

now that you have a json parser, over abuse it!
Move all your table definitions to

note: take away all the {something=nil} construct. Lua will actually remove any nil attribute from the table!

Basic token saving: abuse of the multi-var initializer:
local a,b,c=1,2,3 — saves 1 token per variable

P#55530 2018-08-23 02:27 ( Edited 2018-08-23 06:27)


Yeah, you definitely want to use "table.member=x" under normal circumstances. The alternate method is mainly useful when the member name is a variable, like in the case I showed.

P#55537 2018-08-23 08:25 ( Edited 2018-08-23 12:25)

freds72: Yea that's nice. Didn't occur to me be using the json parser even on those small defines. Cool.

I am currently browsing other carts on the BBS and studying how others go about writing their code. First impression is that most of it is nearly unreadable :p

P#55551 2018-08-23 14:18 ( Edited 2018-08-23 18:18)

The recent tips saved me 265 tokens between 0.6.3 and 0.6.4 :) I'm happy with that.

P#55560 2018-08-23 16:19 ( Edited 2018-08-23 20:19)

It's true that really effective lua code is not always intuitive to read, but that tends to be true in most languages.

Most languages like to give you syntactic sugar that makes things easy to read, but hides under-performing algorithms under the hood, or does redundant stuff for the sake of clarity, stuff like that. This is especially true in object-oriented languages and languages with a lot of first-class support for common data structures and algorithms.

So, when you get to the point where you can't afford those costs, you start having to discard the sugar and write things super-concisely, if not super-readably.

Doing that without making your code unreadable is a true art form, and even with decades of experience, I wouldn't say I've mastered it at all. Most of the time it comes down to this:

Choose one:
A) Readable
B) Efficient (be it token count, performance, etc.)

On PICO-8, the resource limits will make most of the more impressive carts resort to option B.

Mind you, the other end of the spectrum can be just as bad, at least when something is abstracted or made so general/modular that you can't really understand the line of code you're looking at unless you understand everything it calls into, and everything that calls into, and so on. This is where the resource limits on PICO-8 are nice, because they usually force you to avoid over-abstraction.

P#55621 2018-08-24 19:01 ( Edited 2018-08-24 23:11)


You are absolutely correct of course. Even though my code is rapidly getting more and more unreadable it's still maintainable due to the narrow scope and forced limits. I like the concept of this. I work daily with programming but it's without any limits so it requires very little of me and my programming knowledge therefore I haven't focused on optimizations or minimalism. PICO-8 is good practice in that regard.

P#55765 2018-08-27 03:47 ( Edited 2018-08-27 07:47)

Sorry for the lack of updates lately. I've been very busy at work and I also got sidetracked a bit after a fella approached me and wanted to take this concept over to the Atari Lynx platform. I've been aiding him with that the last couple days. I really love the idea of doing this game on real hardware :) This does not mean that I've abandoned the PICO-8 version, far from it. I will soon continue where I left off.

P#56172 2018-09-04 15:18 ( Edited 2018-09-04 19:18)

This looks exciting! Just randomly found it in splore, you've got at least one enthusiastic person who will play it should you finish it! Good luck!

P#61012 2019-01-20 01:05

Are you planning to finish this?

It looks amazing!

P#61015 2019-01-20 01:55 ( Edited 2019-01-20 01:55)

Found this in splore and it’s really nice! I got a runtime error when I equipped (or used?) the book of lightning.

P#96268 2021-08-20 18:03

[Please log in to post a comment]

Follow Lexaloffle:          
Generated 2022-11-26 10:06:21 | 0.031s | Q:70