How to use procedural generation?

There’s been too many updates for me to take everything in so I’m sort of skimming the conversation and replying to bits as I go.


Personally I prefer to do:

if (frame < 1)
	++frame;
else
	frame = 0;

Basically structures are just like a cluster of variables.

For example:

// Using variables
void someFunction()
{
	int x = 0;
	int y = 0;

	x = 5;
	y = 6;
}

Produces the same machine code as:

// Using a struct
struct Coordinate
{
	int x;
	int y;
};


void someFunction()
{
	Coordinate coordinate { 0, 0 };

	coordinate.x = 5;
	coordinate.y = 6;
}

But the latter is better because it demonstrates that there is a relationship between the two variables.

Source code is written for humans, not machines.
Machines are happy with machine code.

As such, it is important for source code to demonstrate the relationships between theoretical entities like points, squares, circles, characters, enemies et cetera.

Code is written to be read by humans, be that the people you are sharing your code with or be it yourself in 6 months time when you’ve forgotten how your code works and need a bit of reminding.
Thus code readability should be one of your top priorities.

Might want a look at this:

It might not all make sense, but hopefully it will make some sense.

It’s a side-on map rather than top down, but the principle is pretty much the same.
The only real differences are the graphics and the gravity.

There’s also some collision detection in there, but it’s done in a bit of an odd way.

Almost nobody does per-pixel collision these days.
Everyone cheats and uses rectangles, circles or polygons.

By the way, from here:

Not only do you not need two Sprites variables, you don’t even need one Sprites variable.

You can simply call any Sprites function like so:

Sprites::drawPlusMask(x, y, sprite, frame);

Also, if you’re using Sprites then you no longer need to use drawSlowXYBitmap.
You should choose between Sprites and arduboy.drawBitmap/arduboy.drawSlowXYBitmap,
there’s no need to use both - using both will eat more progmem.

Also you should try to keep all your graphics in the same format so you only need to use one kind of drawing function.
It makes matters easier.

It’s probably easiest to start with external masks and switch to using PlusMask near the end if you’re sure that you’re able to use PlusMask.

If you need to know more about cellular automata, read about Conway’s Game of Life.
It’s used a lot as an example because it’s really simple to understand and implement and makes the ‘cellular automata’ idea seem much less scary than it sounds.

Fun fact:
Much like Sir Arthur Conan Doyle and Sherlock Holmes,
John Conway despairs that the Game of Life is the thing he’s most famous for because he thinks some of his other work is more interesting and more important.
(There’s a video of him discussing it here.)

Two tips:

  1. Get a duck
  2. Make sure you definitely can’t find the problem before posting

I don’t want to discourage you from posting questions, but if you find you’re solving your own problems before someone replies then clearly the actual problem is just that you’re asking for help a bit too quickly because you doubt your ability to solve the problem.


I’m a bit lost.

  • What is your current objective?
  • What works and what doesn’t work?

Some general advice:
Stick to using uint8_t, int8_t, uint16_t, int16_t and bool over byte, int, boolean et cetera.

Because:

  • int will be different sizes on different systems - on Arduboy it’s 16 bits, on most computers it’s 32 bits, on some computers it’s 64 bits
  • byte and boolean are Arduino-only types
  • bool is the standard boolean type in C++, available on all compilers
  • uint8_t, int8_t, uint16_t, int16_t are standardised C types guaranteed to be defined in any C standard library from C99 onwards as long as the target platform supports them (which is 99% of platforms)
    • The reason it’s the C standard library is that for some strange reason Arduino compiles code as C++ but uses avr-libc (an implementation of the C standard library) instead of an implementation of the C++ standard library

If you don’t know about binary and hexadecimal yet, look here:
https://www.mathsisfun.com/binary-decimal-hexadecimal.html
(One of the few places I trust to explain mathematical topics in human-understandable ways.)

If you don’t know the difference between signed and unsigned numbers, see:

And perhaps:

I find it helps to remember that negation (unary -) is actually implemented (in hardware) as ~x + 1, thus:

  • x = 0001 (1)
  • ~x = 1110 (-2)
  • ~x + 1 = 1111 (-1)
1 Like

Ah, thank you both so much! I’ll sit down and give everything a proper read through (a few times) and I’m sure that’ll help a lot.

And yeahh, I was planning on going back through and changing my sprite formats to all be the same when I go back and animate the rest of the frames (for player movement left/right and up), just kind of been jumping around a bit to learn what I’m lacking so I can go back and tidy it up once I have the base down.

And yeahh, sorry, forgot to mention what I was trying to do in that last bit of code… At this point I’ve got the player animation down and changed the player movement from the player to the background, I was just trying to figure out how to place multiple (random) instances of the same object, to build off of when I get to the cellular automata part, but now I’m thinking it may make more sense to try to use individual pixels and populate a 2 dimensional array (using initRandomSeed), only because the rules for generating that way depend so much on neighboring pixels (as opposed to neighboring blocks, like the sprite I was trying to use). But if I need to use an array to display those sprites anyway then I’ll start there so I understand arrays better (and because I can still use it for enemy placement).

And sorry for the long posts to catch up on, but you’re right, I definitely do doubt my own abilities quite a bit. I’ve gotten a bit past the beginner steps, just still feel like a beginner, and sometimes re reading the same reference material just makes my head numb :stuck_out_tongue: but I’ll hold off on posting in the future and give myself more time to properly process/digest things. I will definitely also keep that in mind about that data types though, too. Thanks again!

Keep in mind that you only have 1K of memory on the Arduboy. It doesn’t take long to run out of RAM - a 20 x 20 tile world will take up about 400 bytes.

Code like the below will work:

enum class Tiles: uint8_t {
  Background,
  Rock,
  SomethingElse
};

Tiles world[20][20];

Then you can assign values like:

world[3][4] = Tiles::Rock;

Finally, you can render the world using code like:

  for ( backgroundx = 0; backgroundx < 20; backgroundx++ ) {
    //For each row in the column
    for ( backgroundy = 0; backgroundy < 20; backgroundy++ ) {

      switch (world[backgroundx][backgroundy]) {

        case Tiles::Background:
          arduboy.drawSlowXYBitmap(..., ..., background, 10, 10, WHITE );
          break;

        case Tiles::Rock:
          arduboy.drawSlowXYBitmap(..., ..., rock, 10, 10, WHITE );
          break;

       .. and so forth.

    }

  }

You can ‘compress’ the world simply by using half bytes (they’re called nibbles!) to half the memory requirements. I can show you how to do that too if it becomes an issue.

2 Likes

In which case I think instead of asking “how to I achieve this specific solution to my problem” perhaps you should be asking “this is what I want to achieve, how do I achieve it and what will I need to know about to achieve it”.
(Or in other words, the old X-Y problem.)

Although not all of it applies to Arduboy’s environment, I recommend this C++ tutorial:
https://www.learncpp.com/
It covers C++11 stuff and is relatively easy to follow.

You don’t need to get concerned with ‘pixels’ but a 2D array is a step in the right direction, as @filmote has just demonstrated.

https://www.learncpp.com/'s chapter 6 covers a lot of useful array stuff (as well as pointers and references).
Chapter 6 starts here.

You could spend 75% of your free time doing programming for an entire year and still feel like a beginner.

Programming is a deep topic that nobody can ever truly master.

As I say, I don’t want to deter you from asking questions, but it pays to make sure you’re asking the right questions and that you actually understand your problem/what you’re asking for.

Stopping and explaining your problem to something inanimate helps you to be sure you know what your problem is.
Sometimes when you’ve identified the actual problem you suddenly realise what the solution is.
Sometimes you realise that the solution you’ve come up with isn’t the best solution.
If you’ve identified the problem and you can’t think of a solution, then you know where the problem lies and what to ask and can include the right context and information.

Though if your question is “how does this language feature/concept work?” then you usually want to ask sooner rather than later, since there’s less ambiguity in those cases - you either understand a concept or you don’t.

If you’re using 1 byte per tile that is. (To make the implicit explicit.)
Using 2 tiles per byte you only use 200 bytes for a 20x20 grid,
but that limits you to just 16 tiles instead of 256.

Either way, RAM is still a big limitation.
That’s probably why we don’t have more tile-based games.

There was some tile rendering code in some of the code I linked to earlier:

1 Like

Nah, that doesn’t deter or discourage me at all. Actually, I have ADHD and executive functioning/planning has just been something I’ve always had issues with… But my other primary Arduino project has helped me with that, because before I started I asked people how they plan an outline for their projects and sat down and did the same for mine. And I was able to work out a lot of details prior to starting, and got a lot more done having a bit more foresight. I’m trying to make this a habit, I just didn’t know enough about arduboy limitations (and some other aspects) before starting this game, so I went back to old habits of dealing with things as they come cuz I got a little lost in the details. But like I said, this is something I’m definitely trying to improve on, so the advice is definitely well received.

Thanks again for the help! I’m gonna try to sit down and give everything a proper outline and better research the parts I need to, then I’ll see where I’m at. All of the above posts will help considerably as well, I’m sure. Thanks again!

1 Like

Hello @CatDadJynx, I like to see someone experiment with procedural generation, I think there is a lot of potential to do things no one has done yet on Arduboy. I have once experimented with procedural map generation on Arduboy, check out this post:

There are a lot of ways to look at procgen – John Carmack has called it basically just a shi**y compression (it always needs to be kept in mind this is a space-time tradeoff when you save memory by real-time uncompressing data and so paying with more CPU work) but then someone comes and creates something like this:

It is a 3D first-person shooter that is completely procedurally generated and fits into 96kb. If you want to do magic with procedural generation, start studying the demo scene. With bytebeat (I think I learned about it on Pokitto forums) you can completely solve the problem of creating music and fitting it to limited memory…

Usually with generating organic procedural things you start with some kind of noise, mostly Perlin Noise (but can be different, such as a Voronoi diagram) – it is a noise, in any number of dimensions, that is like a fractal, i.e. it is similar to itself on different scales, and so it looks similar to clouds or mountains (a big hill that has a number of smaller hills on it with smaller hills on them etc.). You then apply different transforms (distortions, thresholding, mapping to different spaces, combining with mathematical functions, …) to turn it into more advanced things like pretty looking textures, landscapes and so on. Because the noise can be generated in any number of dimensions, you can easily create animations (2D animated noise is actually a 3D noise: 2 spatial dimensions plus 1 time dimension).

All the randomness comes from a single number called seed that is fed to pseudorandom number generator, which is mostly this. By changing the seed you’ll get a different, but similarly looking result, which is nice.

Take a look at NeoTextureEdit – it is a tool that works exactly this way (I have created a similar tool, and one for generating music). Also check out random-art – this might be nice to try to reimplement on Arduboy.

L-systems are another way to procedurally generate stuff – these are often used for generating dungeons. They are also a type of fractal.

There is an interesting game based purely on fractals – Marble Marcher. Perhaps a 2D version of this could be created on Arduboy? :slight_smile: Just an idea.

Hope some of this is helpful!

2 Likes

Thanks, I will definitely look through that once I get there.

As for right now, Ive gotten the random rock placement to work using two randomly populated arrays, then referencing that array each time through the for iteration and deciding to populate or not.

But unfortunately, I went back to try to add all the sprite animations for the other directional inputs and now thats giving me problems. I was hoping i could use an intermediary ‘player’ object to be filled with the appropriate character bitmaps per directional input, but being that the sprites are const unsigned char kept in progmem, i cant seem to find a way to make it work. Last i tried was making the intermediary object static unsigned char player[], but no luck. heres my current code:

// See: https://mlxxxp.github.io/documents/Arduino/libraries/Arduboy2/Doxygen/html/
#include <Arduboy2.h>
Arduboy2 arduboy;
Sprites sprite;

int playerx;
int playery;
int backgroundx = 0;
int backgroundy = 0;
int mapx = 0;
int mapy = 0;
int crashedshipx;
int crashedshipy;
int shiplandedx;
int shiplandedy;
int WORLD_WIDTH = 512;
int WORLD_HEIGHT = 256;
uint16_t rockx[50];
uint16_t rocky[50];
uint8_t rockspawn = 0;
static unsigned char player[];

PROGMEM const unsigned char background[] = {
// Bitmap Image. No transparency
// Width: 10 Height: 16
8, 8, 
0x00, 0x08, 0x80, 0x00, 0x01, 0x20, 0x04, 0x02, 0x04, 0x00, 
0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 
};

const byte shiplanded [] PROGMEM =
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x03, 0xC0, 0x00, 0x00, 0x00, 0x06, 0x20,
  0x00, 0x00, 0x00, 0x0C, 0x10, 0x00, 0x00, 0x00,
  0x7F, 0xFF, 0xFF, 0xC0, 0x00, 0x40, 0x00, 0x00,
  0xFF, 0xC0, 0x47, 0xF8, 0x07, 0xFF, 0xFF, 0x4F,
  0xFF, 0xC7, 0xFF, 0xFF, 0x4F, 0xFF, 0xC3, 0xFF,
  0xFF, 0x47, 0xF8, 0x01, 0xFF, 0xE0, 0x40, 0x00,
  0x00, 0xFF, 0xC0, 0x7F, 0xFF, 0xFF, 0xC0, 0x00,
  0x10, 0x00, 0x02, 0x00, 0x00, 0x14, 0x00, 0x01,
  0x40, 0x00, 0x38, 0x00, 0x03, 0x80, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00
};

PROGMEM const unsigned char crashedship[] = {
// Bitmap Image. No transparency
// Width: 30 Height: 24
30, 24, 
0x00, 0x00, 0x00, 0x00, 0x80, 0xC0, 0x60, 0x30, 0x18, 0x0C, 0x04, 0x08, 0x70, 0x90, 0x10, 0x30, 0x60, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x04, 0x0B, 0x17, 0x2E, 0x5C, 0xB8, 0x70, 0xE1, 0xC2, 0x82, 0x03, 0x04, 0x08, 0x90, 0x20, 0xC0, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x0C, 0x0E, 0x0F, 0x0F, 0x0E, 0x0F, 0x0F, 0x0F, 0x0E, 0x0F, 0x0F, 0x0E, 0x0C, 0x0D, 0x0F, 0x0E, 0x0C, 0x08, 0x08, 0x00, 
};

PROGMEM const unsigned char rock [] = {
  // Sprite: Image + Mask
  // Width: 8 Height: 64
  8, 8,
  0x3C, 0xFF, 0x5A, 0xFF, 0xF3, 0xFF, 0xCB, 0xFF,
  0xBE, 0xFF, 0xE7, 0xFF, 0x7A, 0xFF, 0x3C, 0xFF,
};

PROGMEM const unsigned char playerdown[] = {
  // Sprite: Image + Mask
  // Width: 11 Height: 24
  11, 24,
  //frame 0
  0x00, 0xE0, 0x90, 0xDC, 0x1E, 0xFE, 0x1E, 0xDC, 0x90, 0xE0, 0x00,
  0x00, 0x01, 0x02, 0xFF, 0xC2, 0xFF, 0xC2, 0xFF, 0x02, 0x01, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,

  //frame 1
  0x00, 0xE0, 0x10, 0xDC, 0x1E, 0xFE, 0x1E, 0xDC, 0x90, 0xE0, 0x00,
  0x00, 0x03, 0x05, 0xFF, 0xC2, 0xFF, 0xB2, 0xBF, 0x02, 0x01, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,

  //frame 2
  0x00, 0xE0, 0x90, 0xDC, 0x1E, 0xFE, 0x1E, 0xDC, 0x90, 0xE0, 0x00,
  0x00, 0x01, 0x02, 0xFF, 0xC2, 0xFF, 0xC2, 0xFF, 0x02, 0x01, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,

  //frame 3
  0x00, 0xE0, 0x90, 0xDC, 0x1E, 0xFE, 0x1E, 0xDC, 0x10, 0xE0, 0x00,
  0x00, 0x01, 0x02, 0xBF, 0xB2, 0xFF, 0xC2, 0xFF, 0x0A, 0x07, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
};

PROGMEM const unsigned char playerdown_mask [] = {
  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
  0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
};

PROGMEM const unsigned char playerup[] = {
// Bitmap Image. No transparency
// Width: 11 Height: 24
11, 24, 
0x00, 0xE0, 0x90, 0xDC, 0x1E, 0x1E, 0x1E, 0xDC, 0x90, 0xE0, 0x00, 
0x00, 0x01, 0x02, 0xFF, 0xC2, 0xFE, 0xC2, 0xFF, 0x02, 0x01, 0x00, 
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 

0x00, 0xE0, 0x90, 0xDC, 0x1E, 0x1E, 0x1E, 0xDC, 0x10, 0xE0, 0x00, 
0x00, 0x01, 0x02, 0xBF, 0xB2, 0xFE, 0xC2, 0xFF, 0x0A, 0x07, 0x00, 
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,

0x00, 0xE0, 0x90, 0xDC, 0x1E, 0x1E, 0x1E, 0xDC, 0x90, 0xE0, 0x00, 
0x00, 0x01, 0x02, 0xFF, 0xC2, 0xFE, 0xC2, 0xFF, 0x02, 0x01, 0x00, 
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 

0x00, 0xE0, 0x10, 0xDC, 0x1E, 0x1E, 0x1E, 0xDC, 0x90, 0xE0, 0x00, 
0x00, 0x03, 0x05, 0xFF, 0xC2, 0xFE, 0xB2, 0xBF, 0x02, 0x01, 0x00, 
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
};

uint8_t x = 0;

void setup() {
  arduboy.boot();
  arduboy.initRandomSeed();
  crashedshipx = random(10, 80);
  crashedshipy = random(10, 30);
 for (x = 0; x < 50; x++){
   rockx[x] = random(0,512);
   rocky[x] = random(0,255);
  } 
  arduboy.setFrameRate(75);
  arduboy.clear();
  playerx = crashedshipx + 30;
  playery = crashedshipy + 5;
  // initialize things here
}


uint8_t frame = 0;

void loop() {
  if (!arduboy.nextFrame()) {
    return;
  }
  arduboy.clear();
  for ( backgroundx = 0; backgroundx < WORLD_WIDTH; backgroundx = backgroundx + 10 ) {
    //For each row in the column
    for ( backgroundy = 0; backgroundy < WORLD_HEIGHT; backgroundy = backgroundy + 10 ) {
      //Draw a background tile
      sprite.drawOverwrite(backgroundx + mapx, backgroundy + mapy, background, 0 );
    }
  }

  if (arduboy.pressed(LEFT_BUTTON)) {
    mapx += 1;
  }
  if (arduboy.pressed(RIGHT_BUTTON))  {
    mapx -= 1;
  }
  if (arduboy.pressed(UP_BUTTON)) {
    mapy += 1;
    player = playerup;
    frame++;
    if (frame > 3) frame = 0;
     }
  if (arduboy.pressed(DOWN_BUTTON)) {
    mapy -= 1;
    player = playerdown;
    frame++;
    if (frame > 3) frame = 0;
  }
  else {
    frame = 0;
  }
  sprite.drawExternalMask(playerx, playery, player, playerdown_mask, frame, 0);
  sprite.drawSelfMasked(crashedshipx + mapx, crashedshipy + mapy, crashedship, 0);

  for (rockspawn = 0; rockspawn < 50; rockspawn++) {
    sprite.drawPlusMask(rockx[rockspawn]+mapx, rocky[rockspawn]+mapy, rock, 0);
  }
  arduboy.display();
}

If you change the variable declaration from

static unsigned char player[];

to

uint8_t const *player = nullptr;
1 Like

Your mask doesn’t seem to be working correctly - and a quick look showed most as 0xFF which will block out the background as a rectangle rather than fitting closely around the player.

Your images could be 9 x 16 as none of the bottom row or the first or last columns have any thing in them. You could try the mask below - it looks right to me.

Man

PROGMEM const unsigned char playerdown_mask [] = {
  0x00, 0xf0, 0xf8, 0xfe, 0xff, 0xff, 0xff, 0xfe, 0xf8, 0xf0, 0x00, 
  0x00, 0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0x01, 0x00, 0x00, 
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
};
1 Like

Ah, thanks so much! Ive never used pointers before so I’ll read up more on that as well.

And yeah, when I drew my player sprite I had a couple unused columns and an unused row, just wasn’t sure how it was supposed to work with the masking (but you’re right, it was showing a solid rectangle). There was some black inside the character sprite though, which would need to remain blacked out by the masking. Is 0x00 for a transparent pixel? If so I can just go back in and try to trim it down myself (looks like the image attached to your post above made the sprite all white). Thanks again!

Edit: Sorry, went back and tried to change the line you said above, and the down animation still works fine, but the up animation only changes to the first frame and doesn’t change to the others.

LCG is really easy to understand, but can be very bad for terrain generation because of the patterns it creates when the settings aren’t suitable.

@CatDadJynx

For the record, arrays decaying to pointers is covered in chapter 6 of the learncpp tutorial.
(Specificially chapter 6.8.)
This is why I recommended reading that chapter,
a lot of it is going to be very relevant to what you’re trying to do.

(Also, prefer references over pointers when you can.)

1 Like

The image I attached is the mask - when you use it and the main image, the black sections will show correctly.

Ah, okay, sorry. I’ll go ahead and read through those learncpp tutorials tomorrow (sorry I didn’t already, just a little impatient to get to work, don’t mean to get ahead of myself though :P).

And oh alright, sorry, should have realised that was just the mask, just had a long day -_-

Also, I went through and changed the variable declaration to use the pointer instead, and it worked to change the sprite when the up direction was pressed, but it failed to animate the rest of the frames beyond that (though the down animations still work). I’m not sure if this is because they both use the same frame variable to keep track and it’s getting confused or what, but might try to change that so they each use their own (unless it may be something more obvious).

No problems!

So the lack of animation going up is caused by the else in this code:

  if (arduboy.pressed(UP_BUTTON)) {
    mapy += 1;
    player = playerup;
    frame++;
    if (frame > 3) frame = 0;
     }
  if (arduboy.pressed(DOWN_BUTTON)) {
    mapy -= 1;
    player = playerdown;
    frame++;
    if (frame > 3) frame = 0;
  }
  else {
    frame = 0;
  }

I originally though that only the down was going to be animated. You could simply delete the else or change it to:

  if (!arduboy.pressed(UP_BUTTON) && !arduboy.pressed(DOWN_BUTTON)) { 
    frame = 0;
  }
1 Like

Oh alright, got the other directions working as well, thanks!

1 Like

Two quick bits of info…


Firstly, if you’re using a for loop you should aim to keep the loop variable local.
Maintaining a global variable for a loop counter typically wastes RAM and potentially ends up producing more code and/or slower code.

The compiler generates the best code when every variable exists for no more than the minimum lifetime it needs, so if something can be local then it should be local.

Coincidentally, declaring a variable as close as possible to the point where it’s used makes the code easier to read because the reader ends up with less variables to remember at once, so maintaining the habit of keeping variables close to the point of use is good for both the programmer and the compiler.


Secondly, a demonstration of the use of structs.

At the moment you’ve got:

uint16_t rockx[50];
uint16_t rocky[50];

And you’re using it like:

for (uint8_t index = 0; index < 50; ++index)
{
	Sprites::drawPlusMask(rockx[index] + mapx, rocky[index] + mapy, rock, 0);
}

If you were to create a struct like so:

struct UPoint16
{
	uint16_t x;
	uint16_t y;
};

Then you could do:

UPoint16 rocks[50];

And use it like:

for (uint8_t index = 0; index < 50; ++index)
{
	Sprites::drawPlusMask(rocks[index].x + mapx, rocks[index].y + mapy, rock, 0);
}

Or like:

for (uint8_t index = 0; index < 50; ++index)
{
	// Makes a copy
	const UPoint16 rock = rocks[index];
	Sprites::drawPlusMask(rock.x + mapx, rock.y + mapy, rock, 0);
}

Or even:

for (uint8_t index = 0; index < 50; ++index)
{
	// Makes a reference
	const UPoint16 & rock = rocks[index];
	Sprites::drawPlusMask(rock.x + mapx, rock.y + mapy, rock, 0);
}

Semantically the difference is that you end up treating each rock as a distinct object with a clear semantic link between its component properties instead of just having a group of disparate variables that only have a loose connection.

At a technical level the difference is that instead of having 100 bytes of x values followed by 100 bytes of y values you have the x and y values alternating.

This generally produces better code because CPUs tend to work better when the data they need to work on is contained in a local area (a principle called locality of reference).

Again, this is a case where a technique/practice is good for both the CPU (by producing more optimal code) and the human reading the code (by making the code easier to read/understand).


You don’t have to read absolutely everything, but I pointed you to that chapter specifically because I think it’s going to be the part that you find most useful at the moment.

I find a lot of people starting out tend to get impatient to get stuff working.
Unfortunately programming requires quite a lot of patience.

Scifi films make it seem like you can just tap keys manically for a few minutes and suddenly magic happens,
but in actuality a lot if it is sitting around in deep thought and scribbling notes,
and even seemingly simple things can take time to get working.

I often compare programming to crafts like woodworking, blacksmithing, clock making etc:

  • You sit around and design/figure stuff out first to get a general idea of what you’re going to do
  • When you get to work you use various tools to implement the idea
    • Instead of hammers, chisels and saws you have variables, arrays, loops, functions, classes, templates et cetera
    • At least half the actual skill is in how the tools are used
  • When you’re done you end up with a finished piece that will sit around and do what it does for years to come
    • Although programs can theoretically outlast anything physical as long as you take care not to delete them

Admittedly I think I probably put more effort into the planning phase than most people because I’m a bit of a perfectionist, but still, there’s always at least some planning and thinking ahead.

This is a good thing. I wish more people would do this.

I always try to plan the programs I intend to write in advance because trying to predict the problems I’m going to run into reduces the amount of code that I end up having to rewrite.

I find that just diving in without a plan increases the amount of rewriting needed and the number of bugs that are likely to crop up.

(On my Minesweeper repo I’ve actually included scans of some of my design work. It’s mainly non-technical stuff because I already had prior experience with all of the technical stuff, like the state machine pattern.)

1 Like

I think the struct should be UPoint16 - @Pharap can you edit your post then you can delete this post.

2 Likes

Yes, it should be.
I think I changed my mind half way between writing the examples or something.

No need to delete your comment though,
I’ll just leave it as an example of “even experienced programmers make typing mistakes”.

Ok.

Of course there is already a Point structure in the Arduboy library but its int16_t not uint16_t but I guess the point (no pun intended) was to demonstrate structs.

That’s kind of the point. (Pun intended.)

That too. No point referring to a tucked away struct with (to the uninitiated eye) seemingly no connection to @CatDadJynx’s code.