Maze Runner [V1]

Something like this?
image

1 Like

If there was no mask and the player was drawn over everything, we would be able to see the player all the time as the 6x7 tile for the player had a black background…

This is the snippit for drawing the character. It doesn’t seem the black rectangle I wanted to draw behind the character does anything though.

void Character::printChar() {
  arduboy.drawRect(xFromQuadIndex(index), yFromQuadIndex(index), 6, 7, BLACK);
  arduboy.drawBitmap(xFromQuadIndex(index), yFromQuadIndex(index), snail, 6, 7, WHITE);
}

Yup, like that.

It does. drawRect draws an outline of a rectangle. I believe what you wanted was fillRect.

To be honest though, you’re better off just switching to Sprites format and using Spritess::drawOverwrite instead.
It would probably work out cheaper in the long run.

3 Likes

Sorry it’s been so long. If I learned anything from this project its that level design can be much more time consuming and tedious than writing the actual games,

I’ve added 3 easy stages in the beginning to help introduce some of the mechanics and one hard level at the end.

The last level has dead ends in the bottom maze. I’m unsure if I want to keep that in as a mechanic for the hardest levels or not. You can now return to the level select using A+B so getting stuck ins’t a hard reset.

I still need to implement EEPROM saving as well but otherwise I think after I make a couple more level packs this should be pretty good to go.

Edit:
Is there a current memory map of the standard EEPROM used with the Arduboy2 or is it just the

#define EEPROM_STORAGE_SPACE_START   16

In the arduboy2 library docs?
I only need to save 2 arrays, but as I add more levels the size of those two arrays will increase.

1 Like

There’s been lots of discussion but nothing has ever come of it, beyond allowing you to indicate what you use if you create a .arduboy file. So, you can do whatever you want as long as you don’t go below EEPROM_STORAGE_SPACE_START.

It’s best if you use #defines in an obvious spot, or some other way that makes it easy to change the location(s), in case someone wants to change it and re-compile so it doesn’t interfere with another of their favourite game(s).

Here’s one of the more popular threads:

That’s what I thought but wasn’t entirely sure. What exactly is the 16 that EEPROM_STORAGE_SPACE_START is set too? 16 bytes? Also what would the last location be 1000 then?

Would this be correct for the locations to store my two arrays?

arrayOneSaveLocation = EEPROM_STORAGE_SPACE_START;
array2SaveLocation = arrayOneSaveLocation + sizeOf(arrayOne);

Any EEPROM address below that contains values used by the library that pertain to the overall system and should be carried across sketches, such as the audio mute state and your Arduboy’s unit name should you decide to give it one. You shouldn’t read or write this area directly in a sketch as there’s no guarantee that the layout won’t change in a new library release. There are library calls provided to read or write any values that are deemed allowable for a sketch to change.

The EEPROM is 1K in size, addressed from 0 to 1023. EEPROM_STORAGE_SPACE_START is the first address and 1023 is the last address that a sketch can safely change.

That would work but, unless you need all of it, you might want to add an offset to locate it somewhere farther up in the space.

#define MY_EEPROM_OFFSET 567 // <- some arbitrary value that will still allow the data to fit 

arrayOneSaveLocation = EEPROM_STORAGE_SPACE_START + MY_EEPROM_OFFSET;
array2SaveLocation = arrayOneSaveLocation + sizeOf(arrayOne);

Many existing sketches start right at EEPROM_STORAGE_SPACE_START. By picking a higher address range, you are less likely to clobber or be clobbered by other sketches’ usage of EEPROM space.

1 Like

How does this look?

#pragma once
#include "GameEngine.h"
constexpr uint8_t EEPROM_START_C1                 EEPROM_STORAGE_SPACE_START +373
constexpr uint8_t EEPROM_START_C2                 EEPROM_START_C1 + 1
constexpr uint8_t EEPROM_arrayOneSaveLocationClears = EEPROM_START_C2 + 1;
constexpr uint8_t EEPROM_arrayTwoSaveLocationSteps = arrayOneSaveLocation + sizeOf(arrayOne);

void initEEPROM() {

  unsigned char c1 = EEPROM.read(EEPROM_START_C1);
  uint8_t c2 = EEPROM.read(EEPROM_START_C2);

  if (c1 != ‘V’ || c2 != 93) {    
    EEPROM.update(EEPROM_START_C1, ‘V’);
    EEPROM.update(EEPROM_START_C2, ‘93’);
    EEPROM.put(EEPROM_arrayOneSaveLocationClears, levelsCleared); //bool array of levels beaten
    EEPROM.put(EEPROM_arrayTwoSaveLocationSteps, minSteps);       //uint16_t array of number of steps taken  
  }
}

void saveEEPROMLevel(){
  EEPROM.update(EEPROM_arrayOneSaveLocationClears, levelsCleared);
}

void saveEEPROMSteps(){
  EEPROM.update(EEPROM_arrayTwoSaveLocationSteps, minSteps);
}

Also could you update individual members of a saved array? Something like:

//minSteps is an uint16_t array of length 7
EEPROM.update(EEPROM_arrayTwoSaveLocationSteps + (indexNumber * (*minsteps[1] - *minSteps[0]), minSteps[index]);

You should make those uint16_t since the addresses can be greater than 256.

EEPROM.update will write any single byte to EEPROM wherever you tell it to (if the value saved would change).

You probably already know this but, just in case:

EEPROM has a limited number of times that any given location can be written to before it might start failing to save the correct value. It’s a pretty large number of times, but you don’t want to be writing different values at something like once per second.

For the processor used by the Arduboy it’s specified as minimum 100000 write cycles. You may actually get far more than this but it’s not guaranteed. You can read EEPROM as often and as fast as you wish without risk of damage, though.

there’s a minor risk of c1 and c2 being ‘V’ and 93 from a previous game. It’s very minor, mind you, but just good to be aware of. My games do the same thing. But there’s that teeny risk of someone not being able to save correctly.

1 Like

Woops

I meant more along the lines if that was the right expression. I guess sizeof(minsteps[0]) would work too for the offset.

That’s why I asked about a specific spot. Will EEPROM.update() only write new values to an address or will it always write over whats there? I only intend to call the save functions after a level is completed.

I think the two values will be sufficient enough. They’re more for keeping track of the version, as when I add more maps the two arrays wont line up in EEPROM anymore and need to be changed.

If you’re concerned, you could calculate a checksum and save and check it against the data, for a bit more security (along with also setting a “signature” if desired).

1 Like

Correct. Originally, Arduino only provided EEPROM.write, which always writes to EEPROM, even when unnecessary because the value to be written is what was already last saved at that location. EEPROM.update was added later to avoid that situation, thus sometimes speeding things up and saving wear on the EEPROM if you blindly use values to write.

Note that EEPROM.put is also “intelligent”, like EEPROM.update.

I’m sorry but I’m kind of busy, so haven’t spent time analysing some of your more complicated expressions. I’ve just been reporting more obvious things that I’ve seen.

:+1:

That’s fine, its irrelevant if update can tell not to write the same data twice I don’t need to handle that manually.

1 Like

This is exactly what I did in Minesweeper.

There’s other benefits to using a checksum as well though.
One of the reasons I used a checksum is because it can be used to detect when another game has overwritten your game’s save data.

My implementation is probably a bit overkill for this game because of it’s backwards and forwards compatibility features (and I still need to fix the undefined behaviour) but the checksum approach solves all the problems of the ‘2 byte id’ approach.

Should we ignore the syntax errors?

Sylistically mixing unsigned char and uint8_t is a bad idea.
On the Arduboy they are implemented the same but they actually have different semantics.

uint8_t means “this absolutely must be 8 bits”.
unsigned char means “the smallest addressable unit of memory”.

If in doubt, pretend unsigned char is actually 9 bits (or 10 bits, or 16 bits etc.) rather than 8 bits,
and then ask yourself if the code is still correct.

Also look at what the function actually returns (EEPROM.read returns a uint8_t).

(Also using macro case for anything other than macros is probably a bad idea.)

Yes, but possibly not like that.
Assuming you have an array of uint8_t called array:

EEPROM.update(address + (index * sizeof(array[0])), array[index]);

(Technically the sizeof(array[0]) part isn’t strictly necessary because it should be 1 if your array is an array of uint8_t, but it’s good practise to ‘say what you mean’ and it will be optimised away anyway.)

Personally I try to avoid the EEPROM class and use the eeprom_update_x and eeprom_read_x functions because I’ve found it results in less progmem usage in some situations.

I even wrote my own handy eeprom functions to make it easier to use.
(E.g. it’s possible to do Eeprom::update(address, array) and have the whole array handled without needing sizeof.)

avr-libc has (as far as I’m aware) always provided eeprom_update_byte (et al).

As far as I’m aware EEPROM has never actually been needed,
it’s just a wrapper that introduces (in my opinion) too much indirection and clutter.

Not to say that a wrapper couldn’t be useful,
but the EEPROM library is a very flawed implementation.

1 Like

I suspect that Arduino provides the EEPROM class as an attempt to make EEPROM functions standard across various processor architectures. There no guarantee that the EEPROM functions in avr-libc will be available for other Arduino supported processors.

Had the EEPROMClass class not existed,
all the other architectures would have had to do is implement the missing functions from avr-libc.

There’s only a handful of functions from avr-libc that are surplus to what an implementation of the C standard library would have.
(I say that from experience - I’ve reimplemented almost all of avr-libc for the Pokitto.)