Help me with C++ for my game

The room in program memory/PROGMEM will not be tiled based, but the room when loaded in RAM will be.

The alternative tiles would just be a different id that represents a different image, and what happens (or if nothing happens at all) when colliding with them will be written in the code of monsters and the player.

These are restrictions I can live with

I won’t need all the blocks of a floor loaded at once, just the blocks from a specific room. So I think I won’t need more than 3, maybe I’ll even only need 1. Scratch that, I will only need one in RAM at a time.

normal keys, sword upgrades, boss keys, heart upgrades, a shield and a torch. Nothing else.

There’s an upgradeable health bar the sword slot and the shield

Uniform size

Some traps will be entities others will just be a tile that kills you. I Absolutely agree on the enum class.

Correct

Correct

One of 16 prefabs, 0 is a room too but it has no doors. At least, that’s how the code will behave if the player were to someone be in room layout 0. (there are no actual room prefabs, just door prefabs)

Yes, but the stats won’t be scaled on the players’ stats. They’ll be fixed.

No I think it’ll just be an id. I won’t have more than 16 items and they’ll just be ids. The behavior of these will be in the player code

Ok, that makes sense to a degree.

They’re not just decorative?
What sort of things will they be doing?

If it weren’t for that you could have just had different tile sets and have each dungeon or room pick a tileset so you can reuse the same tile ids.

That means that when leaving a room the block will reset,
so you’ll have to be careful not to trap the player.

Ok. So you probably won’t need an inventory system, that’s good.

Those can just exist as stats, you won’t need an inventory for them.

Then the width and height won’t need to be stored.

That’s a bit vague.

What sort of traps?
Camoflaged entities? Spiked tiles? Arrow shooters? Spinning bars?

So you’re now not calculating doors at runtime?

Or are you calculating them when you load the map?

Ok, this is slowly sounding less like a roguelike and more like just a Zelda game.

In the code, but probably not part of the player class.

Revised…

Dungeon:

  • A dungeon has M monsters
  • A dungeon has O objects
  • A dungeon has N floors

Floor:

  • All floors have the same dimensions
  • A floor has W by H rooms

Room:

  • All rooms are the same dimensions
  • A room can have 1 of 16 door configurations
    • Door configurations are calculated on map load?

Monster:

  • Has a SpeciesType to identify its species, sprite, stats and behaviour

Object:

  • Has a ObjectType to identify its sprite and behaviour

ObjectTypes:

  • Unlocked door
  • Locked door
  • Opened chest
  • Locked chest (normal key)
  • Locked chest (boss key)
  • Locked chest (heart upgrade)
  • Locked chest (sword upgrade)
  • Locked chest (shield)
  • Locked chest (torch)

Will the dungeon reset if the player leaves?
(This isn’t actually to do with storing the map as such,
I’m just starting to think about player stats and saving.)

1 Like

It will be possible to just switch tileset, traps that are static/don’t have a behavior (aren’t entities) will just be an id with a corresponding image, the code for how to react to it will be in the player/monsters code.

Yep. That’s how the old Zeldas did it

Exactly

The width and height isn’t in pixels, it’s in tiles. The width is how many copies in a straight line there is of that tile, this will allow me to have walls in a room that don’t cost much. (this is basically my earlier box idea)

I don’t know yet what my enemies and traps are gonna be (actually I have a pretty good idea about what my traps are gonna be, but not the enemies) but what I do know is that some are stateless, they’re just data - like a hole in the floor. It doesn’t move, it has no behavior, but if you walk on it you fall and die. A spiky block could work the same way, but instead of killing the player just hurting him and pushing him back

So if we ignore the English language, there’s tiles, and there’s entities. The nuance exists only because of how we humans interpret things. Arrow shooters would be an entity, a monster. Spiked tiles would just be a tile id.

Sorry, by doors I meant locked doors “something that requires a key”. Otherwise whether two rooms are connected is implicitly stored in the room layouts. I guess a better word for it would be doorwayLayouts, because I won’t be using actual room prefabs.

I was learning procedural generation from roguelikes, but I was planning on using this for a Zelda-like, as a way to save space.

By the way the traps with special behaviors is why I prefer to use the word entities, since monsters won’t be the only “living” thing stored in the dungeons.

There are no “unlocked doors”, just doorways with a locked door blocking the way or a locked boss door blocking the way. Otherwise it’s just a hallway and is stored in the room/doorway layouts. (I’ll use doorway layout" from now on)

I took a break from programming, this is why I’m replying so late

Sorry I haven’t got round to replying to this properly.
It was on my todo list and then suddenly my week got busier than expected so it’s fallen to the wayside.

I think you’re probably best off to just dive in and have a go since you’ve got most of it figured out.
If a particular solution isn’t suitable it can always be changed later.

1 Like

Hello there!

I decided to continue working on my ARPG/Zelda-like project.

I got a weird bug which I thought I solved, but turns out the project won’t properly upload to the Arduboy, so I guess I didn’t actually solve the problem. When I click “Verify” however, everything is fine, which is why I initially thought I figured it out.

player was a reference to the first index of my Entities array, I checked by doing a player be it’s own entity rather than a reference and everything behaved the exact same way.

So, here’s what the bug was:

exit status 1
too many arguments to function 'void init()'

Which had to do with this line in the main project file:

player:init(EntityType::Player, 22*8, 5*8);

If I removed my arguments it compiled fine, however this function really does take 3 arguments, so that was weird. Then I saw I was using : instead of a single dot . so I replaced it with just a dot.

Now it verifies just fine, but it won’t fully upload to the Arduboy, even after 5 minutes.

Here are the whole code for the three relevant files:

Here's the main file, game_prototype
// Sam Sibbens
// 2018 NOVEMBER 01
// Arduboy RPG Hack and Slash

#include <Arduino.h>
#include <Arduboy2.h>
Arduboy2 arduboy;

#include "Entity.h"
#include "Images.h"
#include "World.h"
#include "View.h"
View view;

constexpr uint8_t MAX_ENTITY_COUNT = 10;
Entity entities[MAX_ENTITY_COUNT];

Entity& player = entities[0];

/* FUNCTIONS DECLARED AND DEFINED IN THIS FILE */
void draw_entities();


void setup() {
    arduboy.begin();
    arduboy.clear();
    arduboy.setFrameRate(30);
    arduboy.initRandomSeed();
    player.init(EntityType::Player, 22*8, 5*8);
}


void loop() {

    //Prevent the Arduboy from running too fast
    if(!arduboy.nextFrame()) {return;}
  
    arduboy.clear();

    //draw_world();
    update_entities();
    view.setPosition(player.x+TILE_HALF_WIDTH, player.y+(TILE_HALF_WIDTH/2));
    view.draw();
    draw_entities();
    arduboy.display();
}

void update_section() {
    for(uint8_t i = 0; i < SECTION_WIDTH_IN_TILES; i++) {
        for(uint8_t j = 0; j < SECTION_HEIGHT_IN_TILES; j++) {
            uint8_t tile = get_tile_progmem(i+view.x, j+view.y);
            set_tile(i, j, tile);
        }
    }
}


/**** FUNCTIONS ****/
void update_entities() {
    for(uint8_t i = 0; i < MAX_ENTITY_COUNT; i++) {
        entities[i].update();
    }
}
void draw_entities() {
    for(uint8_t i = 0; i < MAX_ENTITY_COUNT; i++) {
        entities[i].draw();
    }
}
Here's Entity.h
#pragma once

#include <Arduboy2.h>
#include "World.h"
#include "Images.h"

void draw_walls();


constexpr uint8_t EntityWidth = 8;

enum class EntityType : uint8_t {
    None,
    Player,
    Knight,
    Sword,
};

enum class Direction : uint8_t {
    Right,
    Up,
    Left,
    Down,
};

struct Entity {
    public:
        int16_t x = 0;
        int16_t y = 0;
        EntityType type = EntityType::None;
        uint8_t damage_taken = 0;
        uint8_t state = 0;
        Direction facing = Direction::Down;
    public:
        void init(EntityType t, int16_t xpos, int16_t ypos);
        void move(int8_t xmove, int8_t ymove);
        void update();
        void updateFacing(int8_t xinput, int8_t yinput);
        uint8_t getSprite(EntityType type);
        void draw();
};
Here's Entity.cpp
#include <Arduboy2.h>
#include "World.h"
#include "Images.h"
#include "Entity.h"
#include "View.h"

extern Arduboy2 arduboy;


void Entity::init(EntityType t, int16_t xpos, int16_t ypos) {
    this->type = t;
    this->x = xpos;
    this->y = ypos;
}

void Entity::move(int8_t xmove, int8_t ymove) {
    int16_t xnew = x + xmove;
    int16_t ynew = y + ymove;
    xnew = max(xnew, -TILE_HALF_WIDTH);
    xnew = min(xnew, WORLD_WIDTH_IN_TILES * TILE_WIDTH-1-TILE_HALF_WIDTH);
    ynew = max(ynew, -TILE_HALF_WIDTH);
    ynew = min(ynew, WORLD_HEIGHT_IN_TILES * TILE_WIDTH-1-TILE_HALF_WIDTH);
    if(!::detect_wall_progmem(xnew, y)) {
        x = xnew;
    }
    if(!::detect_wall_progmem(x, ynew)) {
        y = ynew;
    }
}

extern View view;

uint8_t Entity::getSprite(EntityType type) {
    switch (type) {
        case EntityType::Player:
            return &spr_player;
    }
}

void Entity::draw() {
    int16_t drawx = x - view.x * view.width;
    int16_t drawy = y - view.y * view.height;
    switch(type){
        case EntityType::Player:
            Sprites::drawExternalMask(drawx, drawy, spr_player, spr_humanoid_mask, static_cast<uint8_t>(facing), 0);
            break;
        case EntityType::Knight:
            Sprites::drawExternalMask(drawx, drawy, spr_knight, spr_humanoid_mask, static_cast<uint8_t>(facing), 0);
            Sprites::drawExternalMask(drawx, drawy-8, spr_knight_feather, spr_knight_feather_mask, static_cast<uint8_t>(facing), static_cast<uint8_t>(facing));
            break;
        default:
            break;
    }
}

void Entity::updateFacing(int8_t xinput, int8_t yinput) {
    if (xinput != 0) {
        if (0 < xinput) {
            facing = Direction::Right;
        } else {
            facing = Direction::Left;
        }
    } else if (yinput != 0) {
        if (0 < yinput) {
            facing = Direction::Down;
        } else {
            facing = Direction::Up;
        }
    }
}

void Entity::update() {
    int8_t xinput = 0;
    int8_t yinput = 0;
    int8_t speed = 0;
    switch(type){
        case EntityType::Player:
            if(arduboy.pressed(LEFT_BUTTON)) {xinput--;}
            if(arduboy.pressed(RIGHT_BUTTON)) {xinput++;}
            if(arduboy.pressed(UP_BUTTON)) {yinput--;}
            if(arduboy.pressed(DOWN_BUTTON)) {yinput++;}
            updateFacing(xinput, yinput);
            speed = 1;
            break;
        default:
            break;
    }
    if (!(arduboy.pressed(A_BUTTON) || arduboy.pressed(B_BUTTON))) {
        move(xinput*speed, yinput*speed);
    }
}

I assume I had/am having a fundamental misunderstanding of the : for having put that in the first place probably 8 months ago, either that or it was a typo. But I’m not sure why using a . doesn’t seem to really work either.

Any clues?

1 Like

I’m not sure whether you mean ‘making player a global variable’ or ‘using a copy instead of a reference’.

Try uploading something else, like a simple ‘hello world’ program.
Chances are the problem lies is with what’s already been uploaded not being overwritten due to communication issues.
A short ‘hello world’ should be quicker to upload,
and certainly easier to verify (if it says ‘hello world’ then the upload worked).

If ‘flashlight’ mode doesn’t help, try the reset button (it’s hard to get the timing right).

There is one place in C++ where a single colon (:) is used, which is in a ranged for loop,
(e.g. for (auto & value : array) { /*do something*/ },)
but otherwise you should only see double colons (::).
In your case that’s the wrong place for a :: anyway.

:: is the scope resolution operator.
The scope resolution operator (::) is used for accessing a name (a variable, function, type alias, static member variable, static member function or enumerator (enum value)) via a parent scope (a namespace, a class/struct name or a scoped enumeration) - i.e. ‘resolving the scope’.

. is the member access operator.
The member access operator (.) is used for accessing a member (a member variable or member function) of an object.

Perhaps you were getting mixed up with Lua?
(It’s been a while but I think I remember you saying you’ve used Lua before?)

I wasn’t sure how to phrase it, but here is what I meant. Instead of this:

constexpr uint8_t MAX_ENTITY_COUNT = 10;
Entity entities[MAX_ENTITY_COUNT];

Entity& player = entities[0];

Doing this:

Entity player;

Thanks for the clarification about :: and . and : , I thought perhaps I had forgotten something but turns out it really must have been a typo, or maybe I got mixed up with Lua as you said.

Thank you by the way, flashlight mode didn’t work but using the reset button did

I wasn’t coding anything for the Arduboy when I did it, but it’s what I was thinking about when doing it. I learned properly how “Linear-feedback shift registers” works a few months ago. I’ll just have to port my code to have it work, I’ll be able to use it for procedural generation if I still choose to go that way.

I don’t know if people are looking for alternatives to the default function for random numbers, but if it works well, turning it into a tiny library when I’m done could be fun.

1 Like

That would be ‘making player a global object rather than a global reference’.

That seems the more likely to me.
I remember reading something once where a Lua programmer said something like:
“For C++ I think of :: as Lua’s . and . as Lua’s :.”
Which is actually quite a good analogy for the most part.
(For functions at least, less so for variables.)

No problem. I’ve had the exact same problem before.
I don’t know quite what causes it, but it tends to be more frequent with larger and/or more complex programs.

You’re probably doing better than I am then, I only partly understand them.

Might be worthwhile.

Personally if I want to use something other than the built-in random I tend to use xorshift with 13, 17 and 5 because it’s small, has a full 32-bit period and also works well as a hash function.
(It happens to be a type of LFSR.)

If you decide you want to do that and what some help designing and/or documenting the library I might be able to help with that.


You also might be interested in this old thread (if it doesn’t bore you to tears by the end :P).
Some of my statements are a bit off because I didn’t know as much about PRNGs as I do now, particularly the work of Margaslia, but there’s lots of useful stuff there too.

1 Like

I didn’t get bored until comment number 47, which is alright since the thread only has 57 posts :D

It seems people would be interested in a different PRNG, some to save bytes for more game stuff and others for other reasons, but there is interest.

This is actually one reason why I was interested in LFSRs - since a good set of ‘taps’ will result in all the possible numbers except 0, in theory any number could be used as a seed to get unique value. A 16 bit value for x and 16 bits for y position to have a 32 bit seed.

I’m actually a bit rusty so I had to go on Discord and find the old conversation I had when I asked help about LFSRs, and re-read parts of it. I’m pretty grateful for the built-in Discord search function.

Learning how to plan out/design a library could be a very worthwhile thing.

I don’t know how/if it’s possible to only use parts of a library, but if so, having stuff for gradient noise (like Perlin noise) and other things as well in it could be great. A “pseudo random generation” kit. I’m gonna have to make a bunch of things for procedural generation and I’m sure many people would love to use some of it. For example, instead of using a huge array to store their world map, using one seed that they like and only storing changes they’d make to it.

Since you’ve mentioned that in your experience Xorshift worked alright as a hash, I’ll be trying it out for some sort of gradient noise for world generation

Unrelated, but time passes so incredibly fast when reading and writing.

1 Like

I definitely mentioned this idea once to someone here,
but it’s been so long I can’t remember who.

That depends what you mean by ‘use’.

Usually in C++ only the functions you actually use are compiled into the binary executable.
(A.k.a. “You don’t pay for what you don’t use”)
So you can write a large library and only the functions people actually use* will be included in their Arduboy games.

(* I think technically it’s functions that are ‘ODR-used’,
but I’m not sure, the compiler might be able to remove virtual functions.)

It’s an interesting idea, but I think it would be hard to make something that suits everyone’s needs,
so you might have to compromise a bit or provide different alternatives.

That’s one potential use, but bear in mind that you won’t necessarily know what a person’s map system might be like.

Unless you’re planning to also offer specific mapping facilities that is,
in which case I think that would be better off as a separate library.
(‘separation of concerns’.)

Specifically I use it under the name SkullHash (long-ish story) with my checksum calculator to provide checksumming for my save corruption detector in Minesweeper.

The most important factors are (non-exhaustive, in no particular order):
  • Predicting what users will want
  • Predicting what could go wrong
  • Figuring out how to make something as reusable as possible
    • Usually this involves templates
  • Avoiding surprising behaviour
  • Choosing good names
  • Documenting things
  • Adhering to principles like ‘separation of concerns’ and ‘do one thing and do it well’

(Obviously there’s more to it to that,
and there’s certain techniques for achieving these things,
but those are the ‘golden rules’ if you like.)


I’ve actually forseen the eventuality that someone might want to use a custom PRNG,
and as a result of that forsight I made a PR to Arduboy2 that introduces a generateRandomSeed function, which was added in 5.2.0.

How it works (a good lesson in API design)

Basically initRandomSeed used to:

  • Enable analogue-digital-conversion (ADC)
  • Read an unconnected input pin and shift it left 16 bits
  • Read the time since boot in microseconds
  • Add the two values
  • Feed that into randomSeed (similar to srand, possibly equivalent)
  • Disable ADC

generateRandomSeed does almost exactly the same thing,
but rather than feeding the value into randomSeed it returns that value.

initRandomSeed was then changed to randomSeed(generateRandomSeed()),
thus producing more or less the same code as before.

So essentially the seed source is the same,
but now that seed can be used to seed a different PRNG.


That’s good to know, I thought I was imagining it. :P

1 Like

Turns out I was/am very rusty about LFSRs, or I think I was.

I had to read a lot to get familiar again with the concepts, obviously it was much easier than the first time around, but still. However I think I got what I wanted working on my first try, it compiled just fine, and now I’m just in utter disbelief about it. Either I understood it more than I thought and I wasn’t rusty, or I did it all wrong but it compiled anyway. (I definitely did it without following proper programming practices, at least I think). Even if I did everything right, I’m surprised that my C++ was valid at all and compiled since again, I’m feeling extremely rusty.

So far I’ve got this:

uint32_t xorshift32::random() {
    // XOR-ing and bitshifting
    // setting the current seed to the new seed
    this->seed ^= this->seed >> 12; // a
    this->seed ^= this->seed << 25; // b
    this->seed ^= this->seed >> 27; // c
    return this->seed;
}

And, using a working project with SDL2 that I could modify super quick and dirty, I used this function:

void xorshift32::setSeed(uint32_t seedValue) {
    this->seed = seedValue;
}

And a uint32_t global variable counting just adding 1 forever, setting the seed to that, and doing cout on it to see the result:

void Game::update() {
	this->lfsr.setSeed(count);
	std::cout << lfsr.random() << std::endl;
	this->count++;
}

I get outputs like this:

I get outputs like this:

33554433
67108866
100663299
134217733
167772164
201326599
234881030
268435466
301989899
335544328
369098761
402653199
436207630
469762061
503316492
536870932
570425365
603979798
637534231
671088657
704643088
738197523
771751954
805306398
838860831
872415260
905969693
939524123
973078554
1006632985
1040187416
1073741864
1107296297
1140850730
1174405163
1207959597
1241514028
1275068463
1308622894
1342177314
1375731747
1409286176
1442840609
1476395047
1509949478
1543503909
1577058340
1610612796
1644167229
1677721662
1711276095
1744830521
1778384952
1811939387
1845493818
1879048246
1912602679
1946157108
1979711541
2013265971
2046820402
2080374833
2113929264
2147483728
2181038161
2214592594
2248147027
2281701461
2315255892
2348810327
2382364758
2415919194
2449473627
2483028056
2516582489
2550136927
2583691358
2617245789
2650800220
2684354628
2717909061
2751463494
2785017927
2818572353
2852126784
2885681219
2919235650
2952790094
2986344527
3019898956
3053453389
3087007819
3120562250
3154116681
3187671112
3221225592
3254780025
3288334458
3321888891
3355443325
3388997756
3422552191
3456106622
3489661042
3523215475
3556769904
3590324337
3623878775
3657433206
3690987637
3724542068
3758096492
3791650925
3825205358
3858759791
3892314217
3925868648
3959423083
3992977514
4026531942
4060086375
4093640804
4127195237
4160749667
4194304098
4227858529
4261412960
128
33554561
67108994
100663427
134217861
167772292
201326727
234881158
268435594
301990027
335544456
369098889
402653327
436207758
469762189
503316620
536871060
570425493
603979926
637534359
671088785
704643216
738197651
771752082
805306526
838860959
872415388
905969821
939524251
973078682
1006633113
1040187544
1073741992
1107296425
1140850858
1174405291
1207959725
1241514156
1275068591
1308623022
1342177442
1375731875
1409286304
1442840737
1476395175
1509949606
1543504037
1577058468
1610612924
1644167357
1677721790
1711276223
1744830649
1778385080
1811939515
1845493946
1879048374
1912602807
1946157236
1979711669
2013266099
2046820530
2080374961
2113929392
2147483856
2181038289
2214592722
2248147155
2281701589
2315256020
2348810455
2382364886
2415919322
2449473755
2483028184
2516582617
2550137055
2583691486
2617245917
2650800348
2684354756
2717909189
2751463622
2785018055
2818572481
2852126912
2885681347
2919235778
2952790222
2986344655
3019899084
3053453517
3087007947
3120562378
3154116809
3187671240
3221225720
3254780153
3288334586
3321889019
3355443453
3388997884
3422552319
3456106750
3489661170
3523215603
3556770032
3590324465
3623878903
3657433334
3690987765
3724542196
3758096620
3791651053
3825205486
3858759919
3892314345
3925868776
3959423211
3992977642
4026532070
4060086503
4093640932
4127195365
4160749795
4194304226
4227858657
4261413088
256
33554689
67109122
100663555
134217989
167772420
201326855
234881286
268435722
301990155
335544584
369099017
402653455
436207886
469762317
503316748
536871188
570425621
603980054
637534487
671088913
704643344
738197779
771752210
805306654
838861087
872415516
905969949
939524379
973078810
1006633241
1040187672
1073742120
1107296553
1140850986
1174405419
1207959853
1241514284
1275068719
1308623150
1342177570
1375732003
1409286432
1442840865
1476395303
1509949734
1543504165
1577058596
1610613052
1644167485
1677721918
1711276351
1744830777
1778385208
1811939643
1845494074
1879048502
1912602935
1946157364
1979711797
2013266227
2046820658
2080375089
2113929520
2147483984
2181038417
2214592850
2248147283
2281701717
2315256148
2348810583
2382365014
2415919450
2449473883
2483028312
2516582745
2550137183
2583691614
2617246045
2650800476
2684354884
2717909317
2751463750
2785018183
2818572609
2852127040
2885681475
2919235906
2952790350
2986344783
3019899212
3053453645
3087008075
3120562506
3154116937
3187671368
3221225848
3254780281
3288334714
3321889147
3355443581
3388998012
3422552447
3456106878
3489661298
3523215731
3556770160
3590324593
3623879031
3657433462
3690987893
3724542324
3758096748
3791651181
3825205614
3858760047
3892314473
3925868904
3959423339
3992977770
4026532198
4060086631
4093641060
4127195493
4160749923
4194304354
4227858785
4261413216
384
33554817
67109250
100663683
134218117
167772548
201326983
234881414
268435850
301990283
335544712
369099145
402653583
436208014
469762445
503316876
536871316
570425749
603980182
637534615
671089041
704643472
738197907
771752338
805306782
838861215
872415644
905970077
939524507
973078938
1006633369
1040187800
1073742248
1107296681
1140851114
1174405547
1207959981
1241514412
1275068847
1308623278
1342177698
1375732131
1409286560
1442840993
1476395431
1509949862
1543504293
1577058724
1610613180
1644167613
1677722046
1711276479
1744830905
1778385336
1811939771
1845494202
1879048630
1912603063
1946157492
1979711925
2013266355
2046820786
2080375217
2113929648
2147484112
2181038545
2214592978
2248147411
2281701845
2315256276
2348810711
2382365142
2415919578
2449474011
2483028440
2516582873
2550137311
2583691742
2617246173
2650800604
2684355012
2717909445
2751463878
2785018311
2818572737
2852127168
2885681603
2919236034
2952790478
2986344911
3019899340
3053453773
3087008203
3120562634
3154117065
3187671496
3221225976
3254780409
3288334842
3321889275
3355443709
3388998140
3422552575
3456107006
3489661426
3523215859
3556770288
3590324721
3623879159
3657433590
3690988021
3724542452
3758096876
3791651309
3825205742
3858760175
3892314601
3925869032
3959423467
3992977898
4026532326
4060086759
4093641188
4127195621
4160750051
4194304482
4227858913
4261413344
512
33554945
67109378
100663811
134218245
167772676
201327111
234881542
268435978
301990411
335544840
369099273
402653711
436208142
469762573
503317004
536871444
570425877
603980310
637534743
671089169
704643600
738198035
771752466
805306910
838861343
872415772
905970205
939524635
973079066
1006633497
1040187928
1073742376
1107296809
1140851242
1174405675
1207960109
1241514540
1275068975
1308623406
1342177826
1375732259
1409286688
1442841121
1476395559
1509949990
1543504421
1577058852
1610613308
1644167741
1677722174
1711276607
1744831033
1778385464
1811939899
1845494330
1879048758
1912603191
1946157620
1979712053
2013266483
2046820914
2080375345
2113929776
2147484240
2181038673
2214593106
2248147539
2281701973
2315256404
2348810839
2382365270
2415919706
2449474139
2483028568
2516583001
2550137439
2583691870
2617246301
2650800732
2684355140
2717909573
2751464006
2785018439
2818572865
2852127296
2885681731
2919236162
2952790606
2986345039
3019899468
3053453901
3087008331
3120562762
3154117193
3187671624
3221226104
3254780537
3288334970
3321889403
3355443837
3388998268
3422552703
3456107134
3489661554
3523215987
3556770416
3590324849
3623879287
3657433718
3690988149
3724542580
3758097004
3791651437
3825205870
3858760303
3892314729
3925869160
3959423595
3992978026
4026532454
4060086887
4093641316
4127195749
4160750179
4194304610
4227859041
4261413472
640
33555073
67109506
100663939
134218373
167772804
201327239
234881670
268436106
301990539
335544968
369099401
402653839
436208270
469762701
503317132
536871572
570426005
603980438
637534871
671089297
704643728
738198163
771752594
805307038
838861471
872415900
905970333
939524763
973079194
1006633625
1040188056
1073742504
1107296937
1140851370
1174405803
1207960237
1241514668
1275069103
1308623534
1342177954
1375732387
1409286816
1442841249
1476395687
1509950118
1543504549
1577058980
1610613436
1644167869
1677722302
1711276735
1744831161
1778385592
1811940027
1845494458
1879048886
1912603319
1946157748
1979712181
2013266611
2046821042
2080375473
2113929904
2147484368
2181038801
2214593234
2248147667
2281702101
2315256532
2348810967
2382365398
2415919834
2449474267
2483028696
2516583129
2550137567
2583691998
2617246429
2650800860
2684355268
2717909701
2751464134
2785018567
2818572993
2852127424
2885681859
2919236290
2952790734
2986345167
3019899596
3053454029
3087008459
3120562890
3154117321
3187671752
3221226232
3254780665
3288335098
3321889531
3355443965
3388998396
3422552831
3456107262
3489661682
3523216115
3556770544
3590324977
3623879415
3657433846
3690988277
3724542708
3758097132
3791651565
3825205998
3858760431
3892314857
3925869288
3959423723
3992978154
4026532582
4060087015
4093641444
4127195877
4160750307
4194304738
4227859169
4261413600
768
33555201
67109634
100664067
134218501
167772932
201327367
234881798
268436234
301990667
335545096
369099529
402653967
436208398
469762829
503317260
536871700
570426133
603980566
637534999
671089425
704643856
738198291
771752722
805307166
838861599
872416028
905970461
939524891
973079322
1006633753
1040188184
1073742632
1107297065
1140851498
1174405931
1207960365
1241514796
1275069231
1308623662
1342178082
1375732515
1409286944
1442841377
1476395815
1509950246
1543504677
1577059108
1610613564
1644167997
1677722430
1711276863
1744831289
1778385720
1811940155
1845494586
1879049014
1912603447
1946157876
1979712309
2013266739
2046821170
2080375601
2113930032
2147484496
2181038929
2214593362
2248147795
2281702229
2315256660
2348811095
2382365526
2415919962
2449474395
2483028824
2516583257
2550137695
2583692126
2617246557
2650800988
2684355396
2717909829
2751464262
2785018695
2818573121
2852127552
2885681987
2919236418
2952790862
2986345295
3019899724
3053454157
3087008587
3120563018
3154117449
3187671880
3221226360
3254780793
3288335226
3321889659
3355444093
3388998524
3422552959
3456107390
3489661810
3523216243
3556770672
3590325105
3623879543
3657433974
3690988405
3724542836
3758097260
3791651693
3825206126
3858760559
3892314985
3925869416
3959423851
3992978282
4026532710
4060087143
4093641572
4127196005
4160750435
4194304866
4227859297
4261413728
896
33555329
67109762
100664195
134218629
167773060
201327495
234881926
268436362
301990795
335545224
369099657
402654095
436208526
469762957
503317388
536871828
570426261
603980694
637535127
671089553
704643984
738198419
771752850
805307294
838861727
872416156
905970589
939525019
973079450
1006633881
1040188312
1073742760
1107297193
1140851626
1174406059
1207960493
1241514924
1275069359
1308623790
1342178210
1375732643
1409287072
1442841505
1476395943
1509950374
1543504805
1577059236
1610613692
1644168125
1677722558
1711276991
1744831417
1778385848
1811940283
1845494714
1879049142
1912603575
1946158004
1979712437
2013266867
2046821298
2080375729
2113930160
2147484624
2181039057
2214593490
2248147923
2281702357
2315256788
2348811223
2382365654
2415920090
2449474523
2483028952
2516583385
2550137823
2583692254
2617246685
2650801116
2684355524
2717909957
2751464390
2785018823
2818573249
2852127680
2885682115
2919236546
2952790990
2986345423
3019899852
3053454285
3087008715
3120563146
3154117577
3187672008
3221226488
3254780921
3288335354
3321889787
3355444221
3388998652
3422553087
3456107518
3489661938
3523216371
3556770800
3590325233
3623879671
3657434102
3690988533
3724542964
3758097388
3791651821
3825206254
3858760687
3892315113
3925869544
3959423979
3992978410
4026532838
4060087271
4093641700
4127196133
4160750563
4194304994
4227859425
4261413856
1024
33555457
67109890
100664323
134218757
167773188
201327623
234882054
268436490
301990923
335545352
369099785
402654223
436208654
469763085
503317516
536871956
570426389
603980822
637535255
671089681
704644112
738198547
771752978
805307422
838861855
872416284
905970717
939525147
973079578
1006634009
1040188440
1073742888
1107297321
1140851754
1174406187
1207960621
1241515052
1275069487

It looks like it’s working…

If it did work and all I needed was those three lines of code, then I don’t understand why I was so confused about the whole thing. (I did spend a lot of time reading about using different taps and XORing vs XNORing and other useful stuff, but still a big part of the time I spent reading about it was just to get back the understanding of it that I had 8 months ago). Maybe I’m just extremely prone to brain farts.

In other news: here’s a page that links to the .PDF that George Marsaglia wrote about LFSRs: https://www.jstatsoft.org/article/view/v008i14

In the .PDF there’s a list of 8 different lines of code to produce pseudo random numbers from the same three taps, and there’s also a list of about 80 choices of taps we can use.

If I understood correctly, we could use different sets of taps for different things. For example one set of taps for World 1 in a game, a second set of taps for World 2. Just by changing the taps or between the 8 lines of code we should get different outputs, we could make it very cheap to have big worlds.

That’ll wear off soon enough once you get going.

As long as you know about things like the period of a PRNG sequence then you don’t really need to know all the polynomial and tap stuff.

That won’t actually tell you much of interest.
What that tells you is the first entry of each sequence.

The things you need to think about are:

  • Period - how many values can you cycle through before you get back to the start value
  • Uniqueness - how many generated values are unique

The former is easy to test. Using your set-up it should be:

std::uintmax_t testPeriod(std::uint32_t seed)
{
	std::uintmax_t period = 1;
	// I assume you have a constructor that sets the seed
	xorshift32 lfsr { seed };
	auto value = lfsr.random();
	while(value != initial)
	{
		++period;
		value = lfsr.random();
	}
	return period;
}

The latter is much more difficult.
You need a good deal of RAM and something along the lines of a std::bitset.
I know because I ran the test myself on the values in SkullHash.h,
and before I thought to use a bitset I ended up chewing up all the RAM my process was permitted.
(I have 16GB installed, so that’s quite an impressive feat.)

But if you use values from the paper you don’t have to worry about that because Marsaglia’s already tested the values he provides and the whole paper is actually about why those particular values hold those properties (something to do with matrices, I don’t actually understand that part).

Like I say, for xorshift you don’t need to know all that.
If you wanted to do other engines like a Mersenne twister then you might need to know,
but you’d still be better off pulling values from research papers where you can.

Hence the saying “use it or loose it”.

I have it downloaded already from last time I visited it.
I even link to it in SkullHash.h:

Did you pull your 12, 25 and 27 from that page?

If not then you might want to use one of the sets of values Marsaglia lists in his ‘paper’.

Note that his code is under the GPL, but the document itself is under CC BY 3.0,
so if write your own code from scratch and just use the values listed in the paper it’s only the CC BY 3.0 you have to abide by.
(I’ve just realised I should make that clearer in my code,
so I’ve made any issue to remind myself.)

You don’t want to use GPL code, because then you’d have to release your library as GPL,
and then anyone using it would have to release their game as GPL.
That’s what people mean when they call it a ‘viral’ licence.
Things like MIT, BSD-3 et cetera only require that you provide the licence and copyright notice,
they don’t even force you to make your own code open source (wheras the GPL does).

That is indeed more or less how it works.
Though again, that depends how the mapping system works.

You really ought to decide what your goal is.
Your goal will determine how you should approach things.

  • An RPG-ish mapping engine
    • If you only want the mapping engine then you don’t have to worry as much about making things modular and can focus more on the specifics of the engine.
  • A PRNG library
    • If you want to make a PRNG library, you’ll have to think about making it modular and what things other people might want.
  • Both
    • If you want to do both, you have to make the PRNG library modular and make sure you don’t start heavily coupling it to your mapping system or assuming other people will want to use the library in the same way.

You also have to consider how to keep memory usage down.

In the C++ standard library, in <random> all the PRNGs are template classes and the settings are non-type template parameters.
E.g. linear_congruential_engine is:

template<typename UIntType, UIntType a, UIntType c, UIntType m>
class linear_congruential_engine;

However, while using a template-based approachwould probably be cheaper for a single instance of xorshift32,
after 2 or 3 sets of parameters it might be cheaper to use one class that stores the shift values as actual member variables.

One design note: for a PRNG system like this where the transition between states is a single pure function,
I find it’s often more useful to do something like:

// In xorshift32
static uint32_t nextValue(uint32_t value)
{
		value ^= (value << 13);
		value ^= (value >> 17);
		value ^= (value << 5);
		return value;
}

uint32_t nextValue()
{
	this->state = nextValue(this->state);
	return this->state;
}
1 Like

For now I’m not dead set on anything specific, except these few things:

  1. Being able to get a (pseudo) random value based on an X and Y coordinate
  2. Using that as a seed to generate something or using it directly. (The reason I’d be tempted to use it as a seed instead of directly, is that the quantity of random numbers generated from a LFSR is finite, even if it’s huge, and there is a slight chance that two “somethings” at different coordonates could end up being partly generated from a same sequence of numbers. This is especially true if I end up using an implementation with fewer bits.)

I don’t have the specifics down yet, but there’s a chance that I’ll only need to store the state of a PRNG only temporarely while generating a part of whateverItIsIDecideToGenerateLetsSayMazes. So for example generating the layout of a thing, stopping there, keeping it in RAM like a tilemap/2Darray of room layout IDs only. Basically if the seed is the X and Y of a world section, I can use that, generate that section, keep that section in RAM for the player to play in and forget about the seed/state of the PRNG. I have ideas to keep the amount of memory down when it comes to whatever it is I’ll end up doing

In theory I should be able to end up with something very very cool that takes very little space, without busting the RAM limit either. I’ve been thinking about all this a lot :D

For now I’m just focusing on getting some LFSRs right, and then I’ll see if I’m going the right way about all this.

This is very good to know thank you

I have no clue where I got it from, but I’m definitely planning on using values that he found and already confirmed are great to use. For now I just really needed to get something up and running to get out of that brain fog I was in. It’s like I knew about all the theory, but somehow I had to implement it for it to actually click? It was weird

The period is the length of (how many bits * 2) - 1 when using good taps. How good the output is depends, but that’s why I’ll be using the table from the .PDF. I won’t try using my own taps. As for how unique each output is, it’s technically too unique - each number only comes up once. This is why I’ll actually need some other functions to “treat” the output to have something that is usable. Honestly I think I’ll just mention it all as I go along, there is just too much to say. But I promise I am keeping potential weaknesses and problems in mind

OK I found how to write it: the sequence is great, but it’s the same one no matter the seed, the only thing that changes is where you start in the sequence (which is why I should have used the word ‘state’ rather than ‘seed’, as the word seed is very misleading here). However if the sequence is good when ran normally, it doesn’t mean it’s also good when using it like I did here; starting at 0 and incrementing by one to get the next value. Maybe this results in a terrible sequence so I’ll have to check

Honestly I’d say a ton more but I need to test out all that stuff, I’ll report back with any successes and failures I end up with. I’ll double check what you wrote in a bit to see if I forgot to reply to anything

1 Like
That's pretty simple:

Given:

inline uint32_t xorshift32(uint32_t value)
{
	// Or whichever parameters work well
	value ^= (value << 13);
	value ^= (value >> 17);
	value ^= (value << 5);
	return value;
}

And:

// int16_t would also work
uint16_t x;
uint16_t y;

Then:

const uint32_t combinedCoords = ((static_cast<uint32_t>(x) << 16) | (static_cast<uint32_t>(y) << 0));
const uint32_t hash = xorshift32(combinedCoords);

I’ve previously demonstrated this in one of the threads when discussing @CatDadJynx’s rocketship/space project:

Assume for a moment that you’re going to use a 32-bit xorshift with some of Marsaglia’s parameters and the function I provided above that converts two 16-bit x and y coordinates into a 32-bit value.

Given the properties of those xorshift parameters - generating a full period and providing unique outputs for any given input,
that means you could generate around 232 - 1 (-1 for (0, 0)) unique values,
so you really could have a 65536x65536 map effectively stored as a function.

Furthermore, each of those values is a 32-bit value,
so you can make lots of decisions based on those 32-bits.

Size isn’t really the issue here.

This is the real hard part.

Generating good looking ‘things’ from pseudorandom numbers can be difficult,
especially when you don’t have a lot of memory to run with,
and when you consider that using random values means your world won’t necessarily end up with any coherant patterns.

Personally I’d be tempted to split the world into chunks (maybe 8x8 or 16x16 chunks),
have a means of selecting or generating a chunk from a hash of coordinates and then having the world consist of chunks knitted together.

(If you can’t quite imagine that, I’ll draw a diagram.)

I doubt that will be necessary given how much space you’d save by not having to actually store the map.

I once wrote a program that does exactly that.
I managed to dig it out of an old thread, upload it to GitHub and here it is:

NoiseTerrain.hex (27.3 KB)

It can work as long as you’re mindful of how you do it.
E.g. how much RAM you need and how long the terrain generation takes.

(I’m honestly surprised that ‘NoiseTerrain’ runs as fast as it does given how much maths is involved. The original version generated the whole screen every time, making it really slow,
but this version only generates the columns and rows as they’re needed, so it runs much quicker.)

To be honest I think you should just start with an xorshift algorithm or two and start working on things.
Understanding loads about LFSRs isn’t going to make generating terrain any easier.
The LFSR is just a source of good quality pseudo-randomness,
it’s what you do with that randomness that really counts.

I tend to find diagrams help.

You can always discard some bits, or use certain bits for different things.


A few things from some old discussions that might be of interest:

This is exactly what I have in mind :D Using a seed for a section, a section being say an 16x8 area of tiles, and then generating the inside of that section using either a different PRNG or the same one with different tap values.

For dungeons this could already be perfect, for an overworld that makes sense I’d need something different but I have solutions in mind for it. I could write 8 paragraphs about it, but basically using different simple procedural generation algorithms at different scales. Oh and let’s go for it, here’s the essay for anyone interested:

Essay

So, for dungeon generation. There are two main options, one that is already implemented and already works. Wang tiles. It’s already working, the hiccup last time was the built-in random function, it simply wouldn’t behave at all when using X and Y coordonates as seeds. However I tried it just now using the xorshift32 thing (the thing I have may not even be xorshift32, maybe it’s a LFSR, my memory is foggy I’m not sure what the nuance is. Xorshift32 is a specific implementation but that’s far as I know)

Anyway, using the xorshift32 thing with the same taps I was using earlier, getting a seed from that and then setting the built-in Arduboy random function to that seed and then generating a random number between 0 and 15 inclusive, and then using that with the Wang Tile style dungeone generation I already had, it works super well. I don’t get repeating patterns (yet - I haven’t tested what happens when using different values. I might still have to completely forego the built-in Arduboy2 random functions, I don’t know yet.) But point is, before using this to generate a seed, I immediately got a repeating pattern. A repeating pattern in a 18x8 grid of tiles. For whatever reason it wasn’t working at all.

Anyway, for dungeon generation, I could either use Wang Tiles which was my original plan, or I could generate it like people would normally do: start from a corner of the dungeon section and generate it. No wang tiles, nothing fancy, any maze generation algoritm that can fit on the Arduboy would do. The only thing staying in RAM would be an array of 16x8 of the resulting section.

Or we can combine that stuff. Do this one a higher scale, the “dungeon” which could be made of the Wang Tile thing generation algorithm that I have working already (it’s been a long time so here’s a reminder of how it works: in a checkerboard fashion, using the X and Y coordonates as a seed, you generate a random tile among 16 possible tiles. Let’s assume those generated tiles are the black tiles, the white tiles of the chess board are simply whatever fits between the four black tiles surrounding it.) TL:DR this allows potentially infinite number of tiles that will always fit together and will always be the same no matter how you reach a given tile. You can leave and come back and the same thing gets generated.

Anyway, back to the main essay. We can use one algoritm like “broad strokes” of a brush when painting, and a different one for the details. We can have a big world with interesting things at every corner, rather than having to pick between “big and boring” and “interesting but too small”.

On another note, and I’ll mention this quickly - celular automata for cave generation could be used too. If we generate a 10x10 maze which basically ends up being again an array of room layouts (room layout being “which walls of this room has doors?”. They are represented by numbers from 0 to 15 inclusive), we can then generate cave-like walls to it using cellular automata. A single step/update of cellular automata already gives a pretty good cave result, which means it wouldn’t take long at all to generate the cave/dungeon walls of a single section, and by combining it with the maze/dungeon of room layouts, we can make sure that the cave sections/dungeon sections will connect for sure, without needing to generate the whole dungeon/cavern.

Now the question again becomes, how can I use this for the overworld? I’m not sure yet, but I’m pretty confident that the dungeons themselves are pretty much solved, there are some rough corners left that I’ll have to think about but nothing that is worrying me.

Hm… I guess see essay above :D
My only two worries are “how will I do this in C++” and “How will I make this fit on the Arduboy”. These two worries aren’t negligible, but everything else is under controls. I need decent PRNGs however to use whatever algoritm(s) I decide on.

I’ll quote myself for a moment

So yeah, with the built-in Arduboy2 PRNG, for whatever reason I was immediately getting a repeating pattern. I haven’t done enough testing so I am not sure, but it seems that for whatever reason setting the seed to and X and Y value (same way that you suggested), for my Wang Tile dungeon generation, simply wasn’t working well at all. It could be a mathemathical coincidence but I was immediately getting a repeating pattern. Anyway, instead of feeding the position as a seed, if I pass it to the xorshift thing I got, and then use the output of that as a seed to the Arduboy2, then I get good results.

It sounds like you were transforming the X and Y as well to end up with a proper seed to avoid that same problem (using a hash function?)?

Technically I guess I’m using xorshift as a hash function in this case. I could stop there (although I need to do more tests to verify whether or not I get repeating patterns with the built-in random), but I’d like to go further with xorshift.

I’ll need to test if I can use it directly for some value noise/gradient noise for overworld generation without obvious problems. If I use value noise I don’t need to do anything fancy but it won’t be as good as some sort of gradient noise (like Perlin noise).

I remember a very very long time ago we talked about pretty much this, you’ve mentioned brads (byte radiants?) which if it didn’t already exists is what I would have done anyway; 0 to 255 is so similar to 0 to 360. I’ll probably try just value noise first, and only then gradient noise if needed. (from my limited understanding of gradient noise, instead of just using random values and interpolating between them directly, we use distance/direction vector and tweak the results based on those vectors to make everything look either more natural or more twisted, depending on the needs.)

I’ll probably do this with SDL2 since i’ll be able to see a much bigger part of the generated world and more easily notice any patterns or problems.

Well, hopefully someday someone finds all of this information useful. I wrote a ton once more.

1 Like

That would probably be easier.
No need to go overboard with different PRNG engines unless there’s actually a tangible benefit.

Reusing the same algorithm with different values might even use less memory than having two algorithms.
(Assuming the speed would be sufficient.)

Not necessarily.

Remember that a PRNG is an abstract source of randomness,
and the engine is just an implementation of that abstract interface.

What you do with that randomness is just as important,
if not more important than which engine you use.

Even the same thing at different scales can be useful:

Therein lies the problem.

Most maze and dungeon generation algorithms need more RAM than the Arduboy has to spare.
(Particularly because they often involve trees or graphs.)

A drunk walk could work, but that’s slow and doesn’t always look good.
Something like a Lindenmeyer system might be a better bet.

This sort of approach is often used in complex collision systems.
A ‘broad phase’ (e.g. binary space partitioning, quad trees),
and a ‘narrow phase’ (e.g. convex hulls, oriented bounding boxes).

Aye, that too.

Even poor PRNGs with repeating patterns can produce acceptable terrain if used in the right way.

Nature isn’t truly random, it has patterns.

The fibonacci sequence turns up in snail shells and plant heads,
flourites grow into cubes, raindrops form radial patterns,
snowflakes have geometric symmetry.

It’s not actually Arduboy2 that dictates the PRNG, it’s avr-libc,
the C standard library implementation for AVR.
To be honest I’d suggest that you don’t even bother with the built-in random.

It is possible to get access to the source code,
but it’s better to write your own implementation to be sure it’s portable (particularly if you’re going to try to make an SDL2 version),
and that it won’t one day change with an IDE update (e.g. if the authors found a better PRNG and changed the implementation).

If you were using the built-in rand or random then it’s probably a really poor implementation.
Most likely it’s an LCG implementation with crappy parameters.

If you mean in ‘noise terrain’…

I ended up using ((x << 16) | (y << 0)) * (x ^ y)* for some reason,
so it’s possible that just ((x << 16) | (y << 0)) isn’t interesting enough, I can’t really remember.
(* Note that (x ^ y) gives out 0 if x == y, and n * 0 is 0, so you’d end up with a diagonal of zeroes if no further steps were applied.)

I then fed that into the (13, 17, 5) xorshift32 state transition function (i.e. using it as a hash function) before doing various other things (smoothing, value noise/‘octaves’) to create a suitable terrain.

To be pedantic

A ‘seed’ is the initial value fed to a PRNG to start a sequence,
you don’t ‘seed’ a hash function because a hash function is stateless.

‘Binary radians’.

Though as you’ve noticed, they’ve got more in common with degrees than radians.

(Note that the parallel is actually between 0 to 256 and 0 to 360 because 360° == 0° and 256br == 0br. Similarly you could say 255br is like 359°.)

Yes, to begin with you’re probably best off doing the absolute minimum that you need to generate decent results.
Perlin/gradient noise is probably a bit over-complicated.

As I’ve said once before, when it comes to procedural generation, trial and error is usually the better tactic.
You actually have to try things out to decide whether they’re any good or not.

It makes sense to do so, but bear in mind the little nuances between desktop and Arduboy programming.
E.g. int is usually 32-bit on a desktop, but it’s 16-bit on Arduboy.
Try to stick to using the fixed-width types if you can.
(And if you care about portability, remember to qualify them with std:: and use <cstdint> instead of <stdint.h>.)


I’d strongly recommend starting by writing demo programs showing off each technique before trying to knit them together.

Try out some various things to see what kind of patterns you get.
If you see a pattern you like, try to figure out why that pattern is created,
and if you can’t, don’t worry too much, just use it anyway.

Then when you’ve found some things you like, combine them all.


(Sorry if I missed anything, I’ve suddenly had a lot of things to reply to.)

1 Like

Thank you for mentioning it, but I had already thought of it :D

This is my xorshift32.h file that is used in my SDL2 version of it
#pragma once

#include <cstdint>

class xorshift32 {
    private:
        uint32_t seed = 1;
    public:
        void setSeed(uint32_t seedValue);
        uint32_t random();
        uint32_t randomInRange(uint32_t minValue, uint32_t maxValue);
};

I’d love to make a thorough video tutorial series about programming on the Arduboy at some point, I’ve learned a ton thanks to you but it’s spread out among this whole thread, it’d be hard for others to use. I have a decent microphone to record with now, but I still need to improve my C++ before I can start teaching about it (although, teaching others is an excellent way to improve our skill and knowledge about something)

Thank you, I like knowing that kind of stuff. So if I wanted to explain what my thing does, would “It takes the X and Y position as an input and outputs a seemingly random number” ? How can I describe my thing without only calling it a hash? (Or should I just call it a hash function that outputs a unique pseudo random number?)

(Knowing this would make asking for help and googling things easier in the future)

Oh! That explains why I couldn’t find the code for it in the Arduboy2 library

This is very true but I think it is easier to add patterns to a thing than to remove it. I guess separation of concerns apply here somehow; I want a decent enough source of pseudo random numbers, and then I’ll make whatever functions I need to transform the output depending on the needs. Still you’re absolutely right that just because something has patterns, doesn’t mean it’s bad

Yes, kinda like this! Generating the general layout of the maze, maybe in an 8x8 grid fashion, and then generating each section only temporarily when the player enters it.

Problem already solved! :D There’s at minimum one maze algorithm that would work, I actually bought a book on maze algorithms recently. I’m not done reading it so I don’t know which other algorithms (if any) would work on the Arduboy, but at least one would and it is super simple:

For every cell on a grid, toss a coin. If you get tails, remove the wall above the cell, otherwise remove the wall to the right. This type of maze can only result in diagonal paths (or a path straight up or straight right), but with some smart thinking this could be used well. (I tried finding an article or an image of this type of maze but I wasn’t able to, I’ll have to take a screenshot of one myself and show you later). The book Is “Mazes for Programmers” by Jamis Buck

Correct

When you use <cstdint> you should use std::uint32_t instead of uint32_t.
<cstdint> only guarantees that std::uint32_t will exist,
the existance of uint32_t is implementation defined.

(See C compatibility headers.)

Out of interest, does this implementation use % (modulo)?
(And if so, do you know about ‘modulo bias’?)

Unfortunately that’s kind of the nature of the beast.
Programming doesn’t really have a fixed linear progression,
it’s like a tech tree/skill tree, with many many branches.

One of the reasons I’ve never written a tutorial myself is because there’s so many interconnecting ideas that it’s hard to enforce a linear order.

If you have the time, have a read of these:

(Both listed in my resource collection on the C++ page.)

Explain to whom? The intended audience affects how you should explain it.

For a search engine, ‘hash function’ is more likely to net you relevant information.
(Particularly ‘procedural generation hash function’.)

However...

There are at least three different kinds of hash functions: cryptographic hash functions, hash table hash functions and the kind you’ve been using for procedural generation,
each of which has different desirable properties.

(Unfortunately the last case has substantially less coverage because it’s of less general interest. Procedural generation is something that doesn’t appear to have as many applications as hashtables and cryptography.)

The one thing that unites them all is that they take a sequence of bytes (of either fixed or variable size) and output a fixed-size sequence of bits.

In fact Wiktionary’s definition is:

an algorithm that generates a numeric, or fixed-size character output from a variable-sized piece of text or other data; used in database table queries, cryptography and in error-checking

They also all tend to be stateless and desire ‘uniformity’ (“A good hash function should map the expected inputs as evenly as possible over its output range.”),
but I’m reluctant to say that’s true of all hash functions.

Whether you’re explaining to a beginner or an expert,
you should use proper terminology where possible.

If your audience isn’t likely to understand the proper terminology then you should explain it.
For example, if you were explaining what a ‘hash function’ is then you should explain its etymology:

The term “hash” offers a natural analogy with its non-technical meaning (to “chop” or “make a mess” out of something), given how hash functions scramble their input data to derive their output.

If you’re explaining it at a more advanced level then you might want to discuss the expected properties of the function (e.g. being ‘bijective’ - generating unique outputs for each input),
and possibly explain what those properties mean or the implications of them.

That’s quite a loose handwavey explanation,
but it would suffice for a simple introduction to the idea.

PCG Wiki has a good explanation of your use-case:

A hash function is any function that can be used to map digital data of arbitrary size to digital data of fixed size, with slight differences in input data producing very big differences in output data. The output is called a hash value.

Hash functions are particularly interesting for procedural generation for their ability to produce pseudorandom numbers. Unlike Pseudo Random Number Generators, hash function create a random number solely based on an input, with no dependency on previous queries.

Not all hash functions are well suited for procedural generation, as they may either not have sufficiently random distribution, or be unnecessarily expensive, but there are some that are very well suited.

That’s mostly true, but it does depend on the pattern and the operations involved.

For example if you did grid[x][y] ^= (x ^ y)* on every integer value in a grid of values then you’d end up with a checkerboard pattern mixed into the grid,
but applying the same operation again would remove that pattern**.

* This is a well-known trick in the graphics world: https://lodev.org/cgtutor/xortexture.html

** This is because x ^ x == 0. There’s a branch of mathematics that studies properties and relationships like this called ‘group theory’, but that’s a rabbit hole for another time.

It’s a question of resources more than anything else.

It would work, but the caves it generates are probably immensely boring.


As I keep saying, you won’t know whether what you have is good enough until you actually test it in the situation in which you want to use it.

Bottom line: stop reading and researching PRNGs,
start writing demos and testing stuff out.
I want to see some pictures by the end of the week. :P

Like these...

Generated using a C# adaptation of “ArduboyNoiseTerrain”,
with return (hash(value) * (hash(x2) ^ hash(y2))); using xorshift with (13, 17, 5) parameters and uint value = (uint)((x2 << 16) | (y2 << 0));:

size = 8:


size = 16:

size = 32:

size = 64:

Just to prove a point though, here’s what happens if I change the result of the hash(x, y) function:

If I use hash(value) * value I get this naff output:

But if I change that to hash(value) * hash(value) then suddenly:

Even something at tiny as using hash(value) vs value can be the difference between an ugly mess and a realistic-looking island.


Also, I found the article that inspired “ArduboyNoiseTerrain”:
https://lodev.org/cgtutor/randomnoise.html

1 Like

It doesn’t use modulo, and I am aware of the modulo bias, but I’m not sure yet how to go about avoiding it. My current implementation works but if the range is too small it could take a very long time before it returns a valid answer.

(The statistically “best” solution is simply to discard any invalid results and re-run the function until we get a valid output. However since each value only happens once with an LFSR, if the range is too small the function might need to be called over and over again until it has fully lopped back on itself. Although, if I only use some of the bits depending on what the range is, it should make sure that most values will be within range. I could also “pretend” that 0 to the max value of an uint32 is a percentage between 0 and 100 and pass in a percentage value between 0 and 100. There are ways around the problem).

Anyway, until I refactor it, this is what the code is:

uint32_t xorshift32::randomInRange(uint32_t minValue, uint32_t maxValue) {
    uint32_t result = 1;
    if (minValue < maxValue)
    {
        do
        {
            // we substract 1 so that 0 becomes a possible result
            // we use a while loop and throw away any result that is outside the range
            // instead of using the modulo operator because we want to avoid the modulo bias,
            // at least with this function
            result = this->random() - 1;
        } while (!((result >= minValue) && (result < maxValue)));
    }


    return result;
}

I definitely do care about the modulo bias, it becomes especially apparent on smaller ranges. If you know of other solutions I’m all ears, but I think this one is the best (once it is refactored; the actual current code I got has issues as I’ve mentioned)

I would! But I’m tired of doing all my tests in GameMaker, I want to use C++ from now on :D

But, this isn’t a picture but I did implement this maze generation algorithm… in Halo 5. The results are actually pretty decent, and because that type of maze generation is so incredibly fast, I could make a maze that changes its layout as you play in it, similar to the pyramid which changes its layout in Aliens vs. Predator (the movie in Antarctica under the ice). I kept on forgetting to upload this to Youtube, but now I’ve done it. Here it is - North-East Maze Algorithm in Halo 5

A big downside of it is how the very east and very north wall are always ‘open’, but my mirroring the algorithm either on the X or Y axes (and both) we could get an interesting layout. I have some other ideas on how to tweak it.

Combined with the fact that I could make the maze change its layout in real-time every X amount of time or so, this could make for a very interesting dungeon (this was originally my plan for a Predator gametype and map that my friend and I already made; we made the map in a way that would “force” the survivors to move around and get split up, allowing the predator to take them out one by one. A map like this with parts of it being a proceduraly generated maze like this could be very fun). I didn’t phrase myself properly: this time of maze with a self-changing layout was our plan for the next map. I didn’t know this maze algorithm before we made that map.

(Fun fact, the video is unscripted and I was the predator in that game)

Hm. That sounds very similar to what I had came up with for autotiling my tree tiles. I did something along the lined of (if x % 2 == y % 2) then use this tile, otherwise use of of those other tiles. (this all sounds very vague because I actually forgot how I did it)

Here's the value noise based procedural generation of an overworld, which I actually don't _yet_ like, and of which I forgot how I did the autotiling of the tree tiles

Wang Tile based dungeon generator (the plan was that it would correspond 1:1 with the overworld; if there was a cave entrence on the overworld at a given world section, then there would be an exit at that same section in the cave

Here's an example of using cellular automata after a single iteration/update:

Most of these images are from a while back, so you probably will remember some of them, I’ll redo all of this in C++, tweak whatever I need to make things fit on the Arduboy, discard what doesn’t work and keep what does, and make it fun.

As a side note, I might simply ping the author of Ardynia and ask him if he wants to make a new version of his game but fully procedurally generated using whatever I come up with. I’d love to make a game fully on my own but, I loved his game so why recreate something that already exists and is already good if he’d like to collaborate. It would also allow me to focus on all that fancy procedural generation stuff which I like :D

(I might also do both, I haven’t actually asked him yet if he’d be interested. It’s just an idea I’m throwing out there.)

Hum for good measure, here's a last procedural generation algorithm I recently "learned" (in quotes because I think I did it wrong)

This one is “simple” if whoever implements it is familiar with various data structures, it’s basically a binary tree. You divide each section in two, then do the same on the other axis for each of the new sections, until you reach a “room size” that is close to what you want. The advantage of this approach is that there is no need to do collision detection between the rooms, they cannot collide since you only place one room per section. That makes this algorithm very quick. I don’t know if I’ll ever use this, for the Arduboy or otherwise, but it was fun to learn. (the thicker the white lines the more sections are overlapping on that same line, and the green dots and red lines represent the center of each section and to which other section it’s connected to)

Well I’m done for the day, I’ll give an update on whatever I come up with pretty soon

1 Like

There’s a good SO question about it here:


(Note that usually PRNGs use unsigned numbers for simplicity,
so answers using int should probably be using an unsigned equivalent.)

It’s probably not a big issue in your case in your case though,
I just wanted to check that you’re aware of it.

This would certainly work, but I can think of at least one way to make it more efficient:

uint32_t xorshift32::randomInRange(uint32_t maxValue)
{
	// Use a while loop and throw away any result that is outside the range
	// instead of using the modulo operator to avoid the modulo bias.
	while(true)
	{
		// Subtract 1 so that 0 becomes a possible result.
		std::uint32_t result = (this->random() - 1);
		
		if(result < maxValue)
			return result;
	}
}

uint32_t xorshift32::randomInRange(uint32_t minValue, uint32_t maxValue)
{
	// Returns 0 if minValue is greater than maxValue
	return (minValue < maxValue) ? (minValue + uniformRandom(random, (maxValue - minValue))) : 0;
}

There are a number of more efficient solutions,
but it’s hard to get right and most example code isn’t very well documented.

The best way I can think of without doing more research is something like:

uint32_t xorshift32::randomInRange(uint32_t maxValue)
{
	const uint32_t mask = (nextPowerOfTwo(maxValue) - 1);

	while(true)
	{
		uint32_t result = ((this->random() - 1) & mask);
		
		if(result < maxValue)
			return result;
	}
}

By reducing the result with the mask, you drastically cut down the number of discarded results because you end up only looking at the relevant bits.

The problem, however, is how to make an efficient nextPowerOfTwo on AVR.

If min and max are constant then it becomes a lot easier because you can use template parameters and then compute the mask at compile-time instead.

If you’re planning to use more than just xorshift32 then you might be better off turning randomInRange into a templated free function that could be used with all engines.
A while back I wrote something similar for the ‘on randomness’ thread based on the source of arc4random_uniform, though I’m not quite sure how it’s supposed to work:

// Modified version of OpenBSD's arc4random_uniform
template< typename UInt, typename Random >
UInt uniformRandom(Random random, UInt max)
{
	const UInt min = (-max % max);

	UInt result = 0;

	while(result < min)
		result = random();

	return (result % max);
}

template< typename UInt, typename Random >
UInt uniformRandom(Random random, UInt min, UInt max)
{
	return (min + uniformRandom(random, (max - min)));
}

I’d like to point out that this demonstrates why the PRNGs in the C++ standard library overload operator () to provide their random numbers instead of having a random() function.

Given the expression random(), random could either be a function or an object with an overloaded operator (), which makes writing template functions easier.
Objects that overload operator() are often called ‘functors’.

Halo 5 has scripting?
Or is it just a deformable world and you removed blocks manually?

If the latter, this could be done in Minecraft, Garry’s Mod, Scrap Mechanic, Space Engineers…

Actually, Minecraft has function blocks and commands,
so theoretically if you could create/find a source of randomness in Minecraft you could build a maze generator with command blocks and functions.

*Pharap’s ‘to do’ list loudly multiplies*

Simple answer: discard them.
Make the initial grid larger than it needs to be,
run the algorithm, then discard the outer walls.

Or running the algorithm 4 times and then rotating 3 of the resulting mazes.

Stitching them together might be difficult though,
so it might be better to divide a single maze into quadrants and change the direction of travel for each quadrant (up & right for top-right, up & left for top-left et cetera).

That reminds me of the scifi film Cube (and its sequels).
It predates a lot of things with similar ideas.

I’ve never seen any of either franchise.

I can imagine what pattern this would generate.
This is indeed more of a precise checkerboard than the xor pattern.

Aside from the over-abundance of lakes it seems mostly alright.
The trees and grass patches are particularly good.

That seems like a lot of extra work.

It’s easier to just handwave it and say “the caves/dungeons exist in a different dimension”.
(Which reminds me of how Spyro games have portals to levels.)

Much messier than the other approaches.

Might be a good start for islands or something that’s supposed to be messy like grass and dirt, but otherwise not as useful as the other two.

Vaguely. Mostly the trees.

More specifically it’s (axis-aligned) ‘binary space partitioning’, possibly even a k-d tree.