FateHack (heap,object-prototyping roguelike library)

FateHack

Warning! This version uses “remove USB stack” technique. When you upload a new game, you need to hold down the DOWN button while Arduboy is starting up.

Roguelike game and probably tiniest roguelike library for Arduboy or its emulator influenced by NetHack and WildTangent’s FATE.

screen

Bottom panel:

  • 534 free memory left (destroy useless objects if LOW)
  • current year 14Y (now removed)
  • current day 1D (now removed)
  • current hour 1: (now removed)
  • current minute :1 (now removed)
  • current negative elevation 0

Controls:

  • Up , Down , Left , Right - move cursor
  • A or Ctrl + Up , Down , Left , Right - move player
  • A or Ctrl - select object for action or give object to the next potential owner in your possessions
  • B or Alt - toggle menu at cursor position or cancel action
  • to take something ‘on this use player’
  • to drop something ‘on floor . use this’

Inventory list schematic example

1 player1 (has to be placed on scene) <-friend1, friend2(second) and food(third) next owner-+
2 -life:attribute                                                                           |
3 friend1 (has to be placed on scene) <-friend2 and food(second) next owner-+---------------+
4 -dog:type                                                                 |
5 -collar:item                                                              |
6 friend2 (has to be placed on scene) <-food next owner-+-------------------+
7 -miner:type                                           |
8 -life:attribute                                       |
9 -food:item -------------------------------------------+

Game features:

  • saving/loading using seed
  • bot
  • cursor
  • pet
  • toilet
  • thirst
  • priority of things relations over the soulless loot

Programming features for using as library for other Arduboy roguelikes:

  • pathfinding algorithm for generating caves
  • scene builder (no free memory, now commented)
  • object builder
  • dynamic memory allocation for every object (yes, you can run out of memory)
  • OOP pattern at the core - prototype (exemplar)
  • easy scripting for objects interactions

Roguelike library documentation:

Library uses dynamic memory allocation and parametric polymorphism principles. There is no memory for smart pointers implementation so don’t forget to delete objects if you don’t need them anymore. Like this:

Class *c = Class::exemplar.make(object_template);
...some code...
delete c;
c = 0;

Class

Base class and interface for all objects. Is must be used everywhere. Inherit all your new classes from Class.

Class::exemplar

Object maker instance with Print custom tiles functionality.

virtual Class *make(const char* s, uint8_t is_eeprom = 0); //always create all your objects with this method. DONT FORGET TO destroy THEM AFTER! First char is the object type.
virtual void toStr(); //print init string on screen
unsigned int getDigit(char *c, int &s); //get digit after s chars from c. Iterating s
static int hasMoreMemory() { //returns free memory size or 0 if the size is under 300
static void printDebug(char* c); //use this for debuging purposes with itoa
static void setCursor(int8_t x, int8_t y); //Arduboy2 library analog with custom tiles
static void setTextColor(const uint8_t color); //Arduboy2 library analog with custom tiles
static void setTextBackground(const uint8_t color); //Arduboy2 library analog with custom tiles

Class::arduboy

Arduboy2 or Arduboy2Base class instance from Arduboy2 library.

Class::sprites

SpritesB instance from Arduboy2 library.

Scene

Map on the screen. Dynamically allocated array of pointers to Class.

char Scene::getTypeChar()

S

Class *Scene::atPut(Directive key, Class *arg)

Class *Scene::atPut(Directive key, Class *arg) {
  switch (key) {
    case Class::Directive::Find:  //atPut find first clone of Directive::Source (including itself) which can be reached by arg. Then nullify Directive::Source and Directive::Target. Return NULL or found clone. Put Directive::Source before calling this method. TODO: Find closest clone
    case Class::Directive::Reach:  //atPut check Directive::Source can be reached by Directive::Target. Then nullify Directive::Source and Directive::Target. Return NULL or arg. Put Directive::Source and Directive::Target before calling this method.
    case Class::Directive::Source:  //atPut save source pointer. Return NULL
    case Class::Directive::Target:  //atPut save target pointer. Return NULL
    case Class::Directive::Greater:  //atPut set arg to be min path in autogenerated scene. Return this
    case Class::Directive::Less:  //atPut set arg to be max path in autogenerated scene. Return this
    case Class::Directive::Cursor:  //atPut set cursor on owner of arg or return NULL
    case Class::Directive::Near:  //atPut check arg is near the cursor and return arg if true. If false then return NULL
    case Class::Directive::Free:  //atPut check path prototype clone (movable space) is near the arg and return arg if true. If false then return NULL
    case Class::Directive::Owner:  //atPut get next nearest owner in owners tree of arg. Returns pointer to owner if owner exists. Returns arg if arg owns itself
    case Class::Directive::Delete:  //atPut delete arg from every other class on the scene if arg is not scene block. Returns NULL
    case Class::Directive::Move:  //atPut move arg by shortest path. Returns NULL if success, or class that blocks way, or arg if bounds
    case Class::Directive::Turn:  //atPut set next turn to all classes in the scene. Returns NULL
    case Class::Directive::Search:  //atPut find next position of clone of arg (including itself) in the scene and moves cursor to this position. Use atGet ::Cursor to start search from begining. Returns NULL if not found. Returns clone of arg pointer if found
    case Class::Directive::Next:  //atPut clear the scene except arg and its class chain. Returns NULL
    case Class::Directive::Far:  //atPut place arg on scene to be farest by path from Directive::Source to Directive::Target with trying to block that path. TODO: returns
    case Class::Directive::Close:  //atPut place arg on scene to be closest by path from Directive::Source to Directive::Target with trying to block that path. TODO: returns
    case Class::Directive::Mode:  //atPut if arg is not NULL then set blocking mode on. If arg is NULL then off. Returns this
    case Class::Directive::Fill:  //atPut if arg not NULL then set the scene to copy scene blocks istead of referencing the same class instance in every scene block cell. Returns this
    case Class::Directive::Clear:  //atPut clear all clones of arg in the scene. Returns this
    case Class::Directive::Path:  //atPut clone arg and set clone to be path prototype for the scene. Returns this
    case Class::Directive::Reveal:  //atPut if arg is not NULL then reveal explored on this map. if NULL then only near player is revealed
    case Class::Directive::Block:  //atPut clone arg and set clone to be block prototype for the scene. Returns this
    case Class::Directive::Map:  //atPut clear paths and stubs on map and set paths leading to arg. Returns this
    case Class::Directive::Build:  //atPut try to autogenerate the scene and its bounds. Then place arg on scene. Put Directive::X and Directive::Y before calling this method to set scene sizes. If fail then return NULL. Returns this if success
    case Class::Directive::Character:  //atPut place arg under a cursor. Returns arg
    case Class::Directive::X:  //atPut set x coordinate of a cursor within bounds if they exists. Returns NULL
    case Class::Directive::Y:  //atPut set y coordinate of a cursor within bounds if they exists. Returns NULL
    case Class::Directive::Up:  //atPut move arg. Return NULL if success, or class that blocks way, or arg if bounds
    case Class::Directive::Down:  //atPut move arg. Return NULL if success, or class that blocks way, or arg if bounds
    case Class::Directive::Left:  //atPut move arg. Return NULL if success, or class that blocks way, or arg if bounds
    case Class::Directive::Right:  //atPut move arg. Return NULL if success, or class that blocks way, or arg if bounds
    case Class::Directive::Show:  //atPut show scene on screen as arg field of view. Returns NULL
    case Class::Directive::Draw:  //atPut draw scene on the screen for arg
    case Class::Directive::Pass:  //atPut get next class who has turn ignoring arg itself. Returns NULL if no more classes with turns. Returns first class that has turn
    default:
  }
  return 0;
}

Class *Scene::atGet(Directive key)


Class *Scene::atGet(Directive key) {
  switch (key) {
    case Class::Directive::Cursor:  //atGet set cursor at start of scene. Returns NULL
    case Class::Directive::Delete:  //atGet delete player under cursor from scene. Returns NULL
    case Class::Directive::Reveal:  //atGet return NULL if scene not revealed
    case Class::Directive::Block:  //atGet returns block prototype of the scene
    case Class::Directive::Save:  //atGet save cursor position. Returns NULL
    case Class::Directive::Load:  //atGet restore saved cursor position. Returns NULL
    case Class::Directive::Path:  //atGet returns block prototype of the scene
    case Class::Directive::Character:  //atGet returns class under the cursor
    case Class::Directive::Up:  //atGet move the cursor within bounds. Returns NULL if bounds, or y path prototype if moved
    case Class::Directive::Down:  //atGet move the cursor within bounds. Returns NULL if bounds, or y path prototype if moved
    case Class::Directive::Left:  //atGet move the cursor within bounds. Returns NULL if bounds, or X path prototype if moved
    case Class::Directive::Right:  //atGet move the cursor within bounds. Returns NULL if bounds, or X path prototype if moved
    case Class::Directive::X:  //atGet returns x coordinate of a cursor
    case Class::Directive::Y:  //atGet returns y coordinate of a cursor
    case Class::Directive::Build:  //atGet initialize empty scene and its bounds. Call atPut Directive::X and Directive::Y coordinates first before calling this method. Returns this
    default:
  }
  return 0;
}

Player

All objects placed on the scene are Player instances and have other Player in class chain.

int Player::toInt()

Returns script index in _init string.

char Player::getTypeChar()

P

Class *Player::atPut(Directive key, Class *arg)

Class *Player::atPut(Directive key, Class *arg)
{
  switch (key)
  {
    case Class::Directive::Delete: // atPut delete arg from this class chain by pointer. Chain owner not will be deleted. Always return arg
    case Class::Directive::Block: // atPut set this class as path blocker or bind to owner of class chain if arg is not NULL. if arg is NULL then unset
    case Class::Directive::Character: // atPut find clone of arg in this class chain
    case Class::Directive::Next: // atPut push arg to this class chain destructive. Don't use this method. Use Add
    case Class::Directive::Add: // atPut push arg chain to class chain and return arg. Will delete next arg chain before adding if arg not to be placed
    case Class::Directive::Count: // atPut increment counter by one if arg is not NULL. If arg is NULL then set couter to zero
    case Class::Directive::Place: // atPut set this class has to be placed on scene if arg is not NULL. if arg is NULL then unset
    case Class::Directive::Cursed: // atPut set this class has to be placed on scene if arg is not NULL. if arg is NULL then unset
    case Class::Directive::Turn: // atPut set this class has turn in turn order. if arg is NULL then unset
    case Class::Directive::Reveal: // atPut set this class to be hidden on scene. if arg is NULL then reveals
    case Class::Directive::Stack: // atPut set this class to be stacked with same classes. if arg is NULL then not stacked
    default:
  }
  return 0;
}

Class *Player::atGet(Directive key)

Class *Player::atGet(Directive key)
{
  switch (key)
  {
    case Class::Directive::Next: // atGet get next class after this in class chain. Return NULL if end of chain
    case Class::Directive::Turn: // atGet if return NULL then don't have turn
    case Class::Directive::Cursed: // atGet if return NULL then don't have turn
    case Class::Directive::Place: // atGet if return NULL then don't have to be placed on scene
    case Class::Directive::Count: // atGet decrease counter. Return this if counter not zero. Else return NULL
    case Class::Directive::Block: // atGet if return NULL then this class don't blocker or binded
    case Class::Directive::Reveal: // atGet if return NULL then this revealed class
    case Class::Directive::Stack: // atGet if return NULL then this not stacked class. Return this if stacked
    case Class::Directive::Draw: // atGet draw this class symbol on screen
    default:
  }
  return 0;
}

Coordinate

Needed to manipulate scene dimensions and class locations on the scene in polymorphic way.

int Coordinate::toInt()

Integer representation for scene dimension.

char Coordinate::getTypeChar()

C

Class *Coordinate::atPut(Directive key, Class *arg)

Class *Coordinate::atPut(Directive key, Class *arg)
{
  switch (key)
  {
  case Class::Directive::Character: // atPut find clone of arg in first element of class chain
  case Class::Directive::Next: // atPut push arg to this class destructive
  case Class::Directive::Greater: // atPut return greater comparing this and arg
  case Class::Directive::Less: // atPut return less comparing this and arg
  default:
  }
  return 0;
}

Class *Coordinate::atGet(Directive key)

Class *Coordinate::atGet(Directive key)
{
  switch (key)
  {
  case Class::Directive::Next: // atGet get next class after this in class chain
  case Class::Directive::Reveal: // atGet return NULL if hidden
  case Class::Directive::Draw: // atGet draw this class symbol on screen
  case Class::Directive::Up: // atGet move coordinate up
  case Class::Directive::Down: // atGet move coordinate down
  default:
  }
  return 0;
}

How to add your classes:

Inherit from Class and implement interface:

virtual ~Class();
virtual Class *atPut(Directive key, Class *arg);
virtual Class *atGet(Directive key);
virtual Class *clone() const;
virtual Class *make(const char* s);
virtual char* toStr();
virtual int toInt();
virtual char getTypeChar();

Using existing macro:

#define REGISTER_PROTOTYPE(CLASS) static CLASS CLASS##Instance = CLASS(Exemplar())

register your subclass of Class before use:

REGISTER_PROTOTYPE(YourSubClass);

Source code

RogueBeyond.zip (143.6 KB)

HEX

RogueBeyond.ino-arduboy.hex (80.4 KB)

3 Likes

Holy cow this is more than a game this is like a freaking dreadnaught showing up in here, what an amazing piece of code!

1 Like

Thanks! There are many bugs right now, but im working on it. The code now 98% of Arduboy memory with Cathy3K and i think that it will be more potential to run on Arduboy FX. Maybe i can get more space with Cathy2K bootloader but there are errors in old version of Homemade package. As a last resort i can throw away logo sprites entirely.

Actually, your code doesn’t use parametric polymorphism, it uses subtype polymorphism.

  • Parametric polymorphism is what templates achieve.
  • Subtype polymorphism is what inheritance and virtual functions achieve.
  • Ad hoc polymorphism is what function overloading achieves.
  • Sprites should be faster and cheaper than SpritesB.
  • You don’t need an instance of Sprites/SpritesB, you can just use the static functions directly (via the scope resolution operator: ::).

dropDice and getDigit definitely have bugs.

In the case of getDigit, it seems that you’re expecting that when c is passed to getDigit, getDigit will modify c, but that’s not the case at all. getDigit only modifies a copy of c because by default in C++ arguments are passed by value, not by reference. If you want pass-by-reference behaviour, you have to explictly use a reference (e.g. const char * &).

dropDice also seems to be failing to advance c when it should (e.g. after pgm_read_byte_near), and after looking up what ‘fudge dice’ are, I’m not sure the function is implementing them correctly because the operator is being read from the input when it’s supposed to be chosen at random. I.e. each roll of a fudge dice should net you +1, +0/-0 or -1. The way it’s currently being done, "4dF" would net you

But frankly, making your dice rolling function parse a string is a poor use of resources on a resource-constrained device like Arduboy because you have to waste code trying to interpret a string at runtime when the caller could just specify what behaviour they want at compile time.

If you were expecting the string to be provided at runtime, e.g. by a player using a terminal, then parsing a string would make sense, but since it’s something a programmer is going to be providing, you’re better off having a way to express it at runtime.

E.g. int16_t rollFudgeDice(uint8_t count), uint16_t rollDice(uint8_t count), et cetera.

It would actually be possible to be able to write code that would allow a programmer to write 4_dF.roll()/roll(4_dF) using a feature called user-defined literals and some type trickery, though in this case I don’t think it has much benefit over rollFudgeDice(4).

That aside, you’ve got a ton of const violations that the compiler is letting you get away with only because Arduino has the -fPermissive flag. Mainly because you’re using char * where const char * is required. Those could potentially lead to bugs because removing a const is undefined behaviour.

There’s a couple of stylistic faux-pas like using pgm_read_byte_near when pgm_read_byte would suffice, and assigning 0 to pointers instead of nullptr, though those shouldn’t cause bugs.

I get the general impression you might have been following a tutorial that’s possibly a bit outdated.


By the way, you appear to have ‘borrowed’ some code without adhering the the rules of the licences under which the code was published, i.e. providing credit and including a copy of the licence. So far I’ve spotted code from three different authors other than yourself, two of which are currently aware of the fact.

You don’t actually have the right to put the code that isn’t yours in the public domain. You can put your code in the public domain, but not the bits you’ve ‘borrowed’, those have to remain under the licences under which they were published.

Also, if you genuinely do want to put your part of the code in the public domain, the unlicence is potentially a poor choice due to the various issues with it. (Most notably it’s likely to be invalid in Germany.) A licence like CC0 would be more robust.

Either way, if you want to put your code in the public domain, you have to make it clear that the parts that aren’t yours aren’t in the public domain.

1 Like

Thanks for you review! Can i just copy MIT license text from author repository near his code and link URL on GitHub?

1 Like

It wasn’t a proper review as such, just a few issues I spotted that might be the source of some of your bugs.

I’m happy to help you with some of those bugs if you want (provided the licence issue is fixed first), and explain references if you haven’t come across them before (or you could have a read of this tutorial).

Not all of the code is MIT, you’ve code some code from difference sources using different licences.

For the MIT code and BSD 3-clause code you can just keep the notice alongside it and that would be sufficient.

For the Apache 2.0 code you need the licence notice itself, a copy of the licence, and a notice (e.g. a comment) stating that you have modified the code.

I’m not sure where the ‘free memory’ code is from, so I don’t know what the licence is. I’ve seen lots of variants of that code over the years, (I suspect the original came from a book about Arduino,) but until now I’ve not seen one that also accounts for ARM.

If the place you got it from provides a licence, use that. If it doesn’t, you should ideally try to contact the publisher to ask for permission, but at the very least you should provide a comment citing where you got it from.

You might find it easier to denote which code is under which licence if you keep them in separate files, which you should be able to do for at least some of it. You can do that at least for the FlashStringHelper code, the arraySize code and the getFreeMemory code. (You may as well make getFreeMemory a free function anyway, there’s no particular reason to tie it to Class because it’s unrelated to what Class is doing.)

The parts from Font4x6 will be harder to deal with because you’ve actually mixed it into Class, which means you wouldn’t be able to put the whole of Class in the public domain because part of it is copyrighted. You have a few options as to how to deal with that…

The easiest option would definitely be to just release your code under BSD 3-clause so that you’re providing the exact same guarantees and restrictions as the original Font4x6 code.

If you don’t want to do that, the next easiest option would be to move the font handling code into a separate class in a separate file and release just that part as BSD 3-clause. (That might actually help you reduce your memory usage, by reducing the number of functions Class forces its children to inherit. Though even if it doesn’t, it would be a better design, as per separation of concerns.)

The other option is to ask for permission from the author to use the code under a different licence, which is technically a valid option for all cases, but your mileage may vary.

I uploaded new source code without memory info and font until i will decide what to do next.

Without understanding your code too much, I suspect you could just bring in my Font4x6 class as a separate import and use it pretty much as is with your extra characters added.

The added bonus is you have decoupled the font class from your main class!

1 Like

I have removed your font from the source code. You can download it again and see. Now i’m using standart Arduboy2Library font. Can i use Arduboy2Library font without license?

I do not mind you using Font4x6 … its open source for a reason :slight_smile:

1 Like

Upon looking again, it seems I gave you some of the code in an answer a while back and I neglected to mention how the licences would affect things.

At any rate, all you have to do is to get a copy of the relevant .h files, provide them with your code and #include them.

I’ve decided to change FlashStrinHelper.h’s licence to MIT to make it easier for you, so you don’t need to include a copy of the Apache 2.0 licence, you only need the relevant header files.

I also went back and specified in the thread that mentioned pgm_read_point that it’s CC0, so no credit is required for that function.

Here’s a version that has the licenced code moved into different files (except for Font4x6 because that’s more difficult):

FateHack (Redux).zip (73.6 KB)

It also rectifies a few issues where const char *s were being turned into char *s, as well as the issue with getDigit, but not the issues with dropDice.

It would need a few other modifications as well, e.g. write is doing some extra stuff too, but it definitely looks possible.

You can use it without needing to provide the licence yourself, but it still technically has a licence (quite a complex one).

The difference is that the Arduboy2 library is being included externally via the Arduino library mechanism rather than being packaged with your code. Only the person providing the code needs to provide the licence.

Nearly all code is licensed one way or another, because not providing a licence is the same as saying ‘all rights reserved’ - i.e. without a licence, people technically aren’t even allowed to copy the code because of the way copyright law works. (See here for more info.)

1 Like

Thank you! I will add MIT License notice form here mpflaga/Arduino-MemoryFree: basic functions and example to use show used RAM and use less of it. (github.com) to the FreeMemory.h file. Also i want to make Print class to be dependency injection of Class class with fallback Arduboy2 font. That way it will force library user to provide it.

1 Like

There’s several different ways you can do that depending on how Print is used. Unfortunately a lot of them will incur an extra memory cost, one way or another. Some ways are more expensive than others.

Updated sources. Space optimized

1 Like

Sources updated. Huge refactoring. Splitted game sketch from FateHack library (src folder). Walls now can be controlled for one or many instances with Directive::Fill.

1 Like

Source updated. Added another sketch for library debugging. Many fixes.

1 Like

Added saving/loading, throwing, helpers in src folder of FateHack.

1 Like

Added hidden doors and searching

1 Like

Added traps and revealed floors.

2 Likes

Redesigned UI. Added food system.

1 Like