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:

  • current year 14Y
  • current day 1D
  • current hour 1:
  • current minute :1
  • 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 pick(third) next owner-+
2 -life:attribute                                                                           |
3 friend1 (has to be placed on scene) <-friend2 and pick(second) next owner-+---------------+
4 -dog:type                                                                 |
5 -collar:item                                                              |
6 friend2 (has to be placed on scene) <-pick next owner-+-------------------+
7 -miner:type                                           |
8 -life:attribute                                       |
9 -pick:item -------------------------------------------+

Game features:

  • 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
  • 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); //always create all your objects with this method. DONT FORGET TO destroy THEM AFTER! First char is the object type.
virtual char* toStr(); //return init string
static unsigned int getDigit(const char* c); //iterate PROGMEM pointer while number
static void printName(Class * cl); //print name of class
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)

switch (key) {
case Class::Directive::Find: //atPut find reachable CLONE of Directive::Source by arg. Put Directive::Source and Directive::Target (optional) first.
case Class::Directive::Reach: //atPut check Directive::Source is reachable by arg. Put Directive::Source and Directive::Target (optional) first.
case Class::Directive::Source: //atPut save arg pointer in scene object for future operating
case Class::Directive::Target: //atPut save arg pointer in scene object for future operating
case Class::Directive::Greater: //atPut clone arg and set clone to be min path in autogenerated scene
case Class::Directive::Less: //atPut clone arg and set clone to be max path in autogenerated scene
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 0
case Class::Directive::Free: //atPut check path proto is near the arg and return arg if true. if false then return 0
case Class::Directive::Owner: //atPut get next nearest owner of arg in the scene if it exists or return NULL
case Class::Directive::Delete: //atPut delete arg from every other class on the scene if arg is not scene block
case Class::Directive::Move: //atPut move arg by shortest path
case Class::Directive::Turn: //atPut set next turn to all classes in the scene
case Class::Directive::Search: //atPut find next position of clone of arg in the scene and moves cursor. Use atGet ::Cursor to start search again
case Class::Directive::Next: //atPut clear the scene except arg and his classes
case Class::Directive::Far: //atPut place arg on scene to be farest by path from Directive::Source to Directive::Target with blocking path.
case Class::Directive::Close: //atPut place arg on scene to be closest by path from Directive::Source to Directive::Target with blocking path.
case Class::Directive::Mode: //atPut if arg then blocking mode on. if not then off.
case Class::Directive::Clear: //atPut clear all clones of arg in the scene
case Class::Directive::Path: //atPut clone arg and set clone to be path prototype for the scene
case Class::Directive::Block: //atPut clone arg and set clone to be block prototype for the scene
case Class::Directive::Map: //atPut clear paths and stubs on map and set paths leading to arg
case Class::Directive::Build: //atPut try to build the scene and bounds with x and y sizes already setted then place arg there. If fail then return NULL
case Class::Directive::Character: //atPut place arg under a cursor
case Class::Directive::X: //atPut clone x coordinate of a cursor within bounds if they exists. Set this to build initial scene
case Class::Directive::Y: //atPut clone y coordinate of a cursor within bounds if they exists. Set this to build initial scene
case Class::Directive::Up: //atPut move arg. Return NULL if success, class that blocks way, arg if bounds
case Class::Directive::Down: //atPut move arg. Return NULL if success, class that blocks way, arg if bounds
case Class::Directive::Left: //atPut move arg. Return NULL if success, class that blocks way, arg if bounds
case Class::Directive::Right: //atPut move arg. Return NULL if success, class that blocks way, arg if bounds
case Class::Directive::Show: //show scene on screen for spectator arg
case Class::Directive::Draw: //atPut draw scene on the screen for arg
case Class::Directive::Place: //atPut scene stub
}

Class *Scene::atGet(Directive key)

switch (key) {
case Class::Directive::Cursor: //atGet set cursor at start of scene
case Class::Directive::Delete: //atGet delete player under cursor from scene
case Class::Directive::Place: //atGet scene stub
case Class::Directive::Block: //atGet return block prototype of the scene
case Class::Directive::Save: //atGet save cursor position
case Class::Directive::Load: //atGet load cursor position
case Class::Directive::Path: //atGet return block prototype of the scene
case Class::Directive::Turn: //atGet get next class who has turn
case Class::Directive::Character: //atGet get the class under the cursor
case Class::Directive::Up: //atGet move the cursor within bounds. Return NULL if bounds, or y path prototype if moved
case Class::Directive::Down: //atGet move the cursor within bounds. Return NULL if bounds, or y path prototype if moved
case Class::Directive::Left: //atGet move the cursor within bounds. Return NULL if bounds, or y path prototype if moved
case Class::Directive::Right: //atGet move the cursor within bounds. Return NULL if bounds, or y path prototype if moved
case Class::Directive::X: //atGet get x coordinate of a cursor
case Class::Directive::Y: //atGet get y coordinate of a cursor
case Class::Directive::Build: //atGet initialize empty scene and its bounds. Set X and Y coordinates first before calling this method
}

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)

switch (key) {
case Class::Directive::Delete: //atPut delete arg from this class chain by pointer ignoring chain owner
case Class::Directive::Count: //atPut set effect counter to integer from arg. if NULL set effect counter to 0
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. Delete next arg chain before adding if arg not to be placed
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::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
}

Class *Player::atGet(Directive key)

switch (key) {
case Class::Directive::Next: //atGet get next class after this in class chain
case Class::Directive::Turn: //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 effect duration. Return this if duration not ended. If ended then 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::Draw: //atGet draw this class symbol on screen
}

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)

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
}

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
}

Stub

Needed for memory economy. Replaces coordinates.

char Stub::getTypeChar()

A

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

FateHack.zip (30.0 KB)

HEX

FateHack.ino-arduboy.hex (77.8 KB)

2 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.