PicoSystem C++ Map help

I have created a table in Lua for Pico-8 in order to draw a row of invaders every frame ! (see attached images - code/result)


I’m trying to do the same thing with a map (as it seems to be the closest data type to a table in Lua) in C++ and I’m having trouble understanding/figuring out the syntax to do this, I also need to create the for loop to iterate through it and display my sprites.

Here’s my code so far:


// Map, C++, PicoSystem

#include “picosystem.hpp”

#include
#include
#include
#include
using namespace picosystem;

using namespace std;

int px = 57;
int py = 57;
int score = 0;

// for sound
voice_t blip;

// Custom sprite sheet
const color_t custom_sprite_sheet_data[256] = {
0x0000, 0xffff, 0xffff, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0x0000, 0x0000,
0xffff, 0x0000, 0xffff, 0xffff, 0x0000, 0x0000, 0x0000, 0x0000, 0xffff, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xffff, 0x0000,
0xffff, 0xffff, 0xffff, 0xffff, 0x0000, 0x0000, 0x0000, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000,
0xffff, 0xffff, 0xffff, 0xffff, 0x0000, 0x0000, 0x0000, 0x0000, 0xffff, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xffff, 0x0000,
0xffff, 0xffff, 0xffff, 0xffff, 0x0000, 0x0000, 0x0000, 0x0000, 0xffff, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xffff, 0x0000,
0xffff, 0xffff, 0x0000, 0xffff, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0x0000, 0x0000,
0xffff, 0x0000, 0x0000, 0xffff, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0xffff, 0xffff, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000
};

// spritesheet setup
buffer_t *CUSTOM_SPRITESHEET = buffer(16, 16, (void *)custom_sprite_sheet_data);
buffer_t *custom_sprite_sheet = CUSTOM_SPRITESHEET;

// initialize function
void init() {
blip = voice(10, 10, 10, 10, 40, 2);

// empty map container
map<string, int> coin;
// elements
coin.insert[0](pair<string, int>(“x”, 75, “y”, 78);
coin.insert[1](pair<string, int>(“x”, 30, “y”, 33);
coin.insert[2](pair<string, int>(“x”, 55, “y”, 18);
coin.insert[3](pair<string, int>(“x”, 10, “y”, 73);
coin.insert[4](pair<string, int>(“x”, 80, “y”, 28);
}

// update function (called 50 times a second)
void update(uint32_t tick) {
if(button(LEFT)) {
if(px > 2) {
px -= 2;
}
}
if(button(RIGHT)) {
if(px < 113) {
px += 2;
}
}
if(button(UP)) {
if(py > 1) {
py -= 2;
}
}
if(button(DOWN)) {
if(py < 113) {
py += 2;
}
}

// check player collision with coins later

}

// draw function
void draw(uint32_t tick) {
// set current color
pen(0, 0, 0);
// clear screen with current color
clear();

pen(15, 15, 15); // color white
text(“SCORE:”, 2, 3);
text(str((int)score, 0), 41, 3);

// load custom spritesheet
spritesheet(custom_sprite_sheet);

// draw player
sprite(1, px, py);

// draw coins using data from map in these spots (see map above)
//sprite(0, 75, 78);
//sprite(0, 30, 33);
//sprite(0, 55, 18);
//sprite(0, 10, 73);
//sprite(0, 80, 28);
}


As you can see, I need help to fill in the for loop and the sprite number?, x?, y?
I have a pretty good C++ book and have read many samples online of how this should work but I haven’t been able to come up with a solution that works, any help would be appreciated.

Thanks,

Brian

One approach to consider is a change to the data structures - it does sound like map is the closest to Lua’s table; but, given the current data/usage, it looks like a vector would work ok here. I’d also consider defining a struct to hold the sprite data.

So, something like so:

struct CoinData {
  int x;
  int y;
};

// empty vector container
vector<CoinData> all_coins;

(which would both live outside any of the functions so they are available in both the init and draw functions).

Then, the data population in the init function could look like this (there are some other ways to get the vector's contents defined; but, this would be a starting point):

  all_coins.push_back({.x = 75, .y = 78});
  all_coins.push_back({.x = 30, .y = 33});
  all_coins.push_back({.x = 55, .y = 18});
  all_coins.push_back({.x = 10, .y = 73});
  all_coins.push_back({.x = 80, .y = 28});

And then, for looping/drawing, something like the following in the draw method:

 for (CoinData coin : all_coins) {
   sprite(0, coin.x, coin.y);
 }

-Dan

Thanks Dan, I’ll try this out ASAP, I almost went to you directly but didn’t want to bother you ;) I really do appreciate all your help.

Brian

1 Like

It works great, thanks Dan. It works without #include <vector, should I make sure to include it anyhow in order to take full advantage of the vector?

Brian

1 Like

You’re welcome Brian, no worries! I like the PicoSystem, so I’m hoping if we all share our challenges and experiences as we go, the ecosystem will grow :)

Glad to hear that worked! My thought on the include is that by including "picosystem.hpp", the code is picking up that file’s include of <vector>:

If it were me, I’d keep the explicit include of <vector> in the game file - both to call out what’s being used directly and to be insulated in case the PicoSystem API is ever refactored to remove the usage of vector.

  • Dan

Thanks, is there a difference between: vector myvector; and std::vector myvector;

All of the examples I see online to iterate through a vector and delete something (or otherwise make a change use std:: for setting up and calling out, etc. It looks like the using namespace std; keeps us from having to use the std:: in front of calls.

So by using namespace std can I just eliminate the std:: where I see it?

I created a loop to check for player/coin collision and delete the coin, the collision works perfectly and the score increases but my erase isn’t working - no doubt syntax.

I’ve tried:
erase(coin);
all_coins.erase(coin);
erase(all_coins.coin);
all_coins.erase(all_coins.coin);

Brian


// Vector, C++, PicoSystem

#include “picosystem.hpp”
#include
//#include
//#include
//#include
//#include

using namespace picosystem;
using namespace std; // also needed to use a vector

int px = 57;
int py = 57;
int score = 0;

// for sound
voice_t blip;

// Custom sprite sheet
const color_t custom_sprite_sheet_data[256] = {
0x0000, 0xffff, 0xffff, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0x0000, 0x0000,
0xffff, 0x0000, 0xffff, 0xffff, 0x0000, 0x0000, 0x0000, 0x0000, 0xffff, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xffff, 0x0000,
0xffff, 0xffff, 0xffff, 0xffff, 0x0000, 0x0000, 0x0000, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000,
0xffff, 0xffff, 0xffff, 0xffff, 0x0000, 0x0000, 0x0000, 0x0000, 0xffff, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xffff, 0x0000,
0xffff, 0xffff, 0xffff, 0xffff, 0x0000, 0x0000, 0x0000, 0x0000, 0xffff, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xffff, 0x0000,
0xffff, 0xffff, 0x0000, 0xffff, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0x0000, 0x0000,
0xffff, 0x0000, 0x0000, 0xffff, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0xffff, 0xffff, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000
};

// spritesheet setup
buffer_t *CUSTOM_SPRITESHEET = buffer(16, 16, (void *)custom_sprite_sheet_data);
buffer_t *custom_sprite_sheet = CUSTOM_SPRITESHEET;

// struct for coins
struct CoinData {
int x;
int y;
};

// empty vector container
vector all_coins;

// initialize function
void init() {
blip = voice(10, 10, 10, 10, 40, 2);

// add coins to CoinData
all_coins.push_back({.x = 75, .y = 78});
all_coins.push_back({.x = 30, .y = 33});
all_coins.push_back({.x = 55, .y = 18});
all_coins.push_back({.x = 10, .y = 73});
all_coins.push_back({.x = 80, .y = 28});

}

// update function (called 50 times a second)
void update(uint32_t tick) {
if(button(LEFT)) {
if(px > 2) {
px -= 2;
}
}
if(button(RIGHT)) {
if(px < 113) {
px += 2;
}
}
if(button(UP)) {
if(py > 1) {
py -= 2;
}
}
if(button(DOWN)) {
if(py < 113) {
py += 2;
}
}

// check player collision with coins - then delete/erase
for (CoinData coin : all_coins) {
if(px + 7 > coin.x and px < coin.x + 4 and
py + 6 > coin.y and py < coin.y + 8) {
//all_coins.erase(coin);
score += 10;
}
}

}

// draw function
void draw(uint32_t tick) {
// set current color
pen(0, 0, 0);
// clear screen with current color
clear();

pen(15, 15, 15); // color white
text(“SCORE:”, 2, 3);
text(str((int)score, 0), 41, 3);

// load custom spritesheet
spritesheet(custom_sprite_sheet);

// draw player
sprite(1, px, py);

// draw coins using data from vector (above)
for (CoinData coin : all_coins) {
sprite(0, coin.x, coin.y);
}

}

For the namespacing, that’s my understanding - that as long there’s a using namespace std line, it’s fine to leave off the std:: prefix. I think where this could change is if the code needs to use two different namespaces which each declare something with the same name (E.G. std::vector and some_library::vector where there would be a collision without the prefix).

For the erase section, one approach would be to switch to using a for loop and then using the index of the entry that has the collision in the call to erase. Something like the following:

 // check player collision with coins - then delete/erase
 for (int i=0; i<all_coins.size(); i++) {
   CoinData coin = all_coins[i];
   if (px + 7 > coin.x and px < coin.x + 4 and
      py + 6 > coin.y and py < coin.y + 8) {

     all_coins.erase(all_coins.begin() + i);
     score += 10;
     break;
   }
 }

The syntax on erase was a bit of a surprise for me honestly. It seems like it needs to have an iterator like vector::begin() but then it’s fair game to add to that iterator to get to the index one actually wants to remove. The break is there to exit from the loop after the removal since the size will have changed due to the modification.

Thanks Dan, works great, I would never have figured this one out on my own. Sorry, I just realized that that my tabs were non existent in my pasted code - must have been hard to read.

I originally started with a for loop but then thought I could use the format used for drawing the sprites, I got close but just could not figure out how to get the erase to work. This is still pretty clean and the important thing is I actually understand it.

I recently retired (during covid) and took up coding as a hobby - lot to learn! I worked for over 20 years in the video game industry as an artist, animator and art director, I started way back in the day on a Commodore 64 writing slow/bad games in basic, I thought I would be a programmer but found that I was better at art and animation. ;)

I’ve been buying up all the little handheld programmable devices and writing games on them, at some point I’m going to make a video for YouTube to document it. I started with Pico-8 with Lua, I also have a Playdate, Gamebuino, Arduboy, Pygamer, Pocket Arcade, PicoSystem and ordered a Thumby, I used to work with Unreal and Unity professionally – but retro stuff is way more interesting to work on.

I even went back and wrote a game in Batari Basic for the Atari 2600 (and ran it off a cartridge), wrote a game in assembly for the C64 and fooled around with C on the C64 and Amiga.

I just love this stuff, check out the Thumby if you haven’t seen/heard of it:

https://tinycircuits.github.io/ - the IDE runs on in a browser, it’s really fun to mess with.

Anyhow thanks again for your help, it’s this kind of community that makes doing this so cool!

Brian

1 Like

Hey Brian, glad that worked/made sense and you’re welcome, no worries :) Definitely agree with the appeal of the retro systems and the communities around them! I’ve found when I try to write games with the more modern frameworks like Unity, I get overwhelmed by the number of options / size of the feature set - there’s something appealing about working within the constraints of the smaller systems.

My first computer was a TI-99/4A and I spent a bunch of time writing slow/bad games in basic as well :) I ended up sticking with the programming, but only occasionally dabbling in trying to write games as a hobby.

I’ve got a Playdate on order, looking forward to trying that out and seeing what people build for it. I’ll definitely check out the Thumby, that looks pretty awesome! I’ll be interested in seeing the YouTube video documenting your experience with the various systems.

-Dan

Hey Dan, here’s my demo, I just have to add some better/more sound/s. This is just a demo with a single invader with:

  • A controllable player that can fire a projectile
  • A moving animated enemy who also fires a projectile
  • Collision detection
  • Keeping track of score/lives and displaying it
  • Game states (game running, game over)

Later I’ll add the Vector for several rows of sprites, just had to get the basics down first.

Hey Brian, that’s looking good - I particularly like the animation on the sprites! Controls feel nicely responsive as well.

The game I’m currently working on is a variant of Wumpus, it’s a work in progress (I’m currently working on adding sound and still need to tackle a bunch of things - having a maze instead of a pure grid, refine some screens, etc.), but it should be playable at this point (D-pad to move between rooms, any button to switch between shooting mode and moving mode).

-Dan

Very cool! nice job on the graphics!

1 Like

Thanks! They’re heavily inspired by the TI-99/4A version (which was my first introduction to Wumpus :) )

I remember that well, when I had my C64, a neighbor friend of mine had Ti994a, it was a nice looking machine.

1 Like

Hey Dan, what machine/OS are you using for your PicoSystem development, I’m using a Raspberry Pi 4 with Twister OS.

Brian

Hey Brian, I’m currently using an Ubuntu VM managed by Vagrant on OSX for development. It’s a relatively small Vagrantfile:

Vagrant.configure("2") do |config|
  config.vm.box = "ubuntu/focal64"

  config.vm.provision "shell", inline: <<-SHELL
    apt-get update
    apt-get upgrade -y

    # pico requirements
    apt-get -y install build-essential \
                       cmake \
                       gcc-arm-none-eabi \
                       libnewlib-arm-none-eabi \
                       libstdc++-arm-none-eabi-newlib \
                       python3-pip
    pip3 install 32blit
  SHELL

  config.vm.provision "shell", privileged: false, env: {"PICO_SDK_PATH" => "~vagrant/pico-sdk"}, inline: <<-SHELL
    # pico sdk
    git clone https://github.com/raspberrypi/pico-sdk.git ~/pico-sdk
    echo 'export PICO_SDK_PATH="~vagrant/pico-sdk"' >> ~/.bashrc
    cd ~/pico-sdk && \
      git submodule update --init

    # pico system
    git clone https://github.com/pimoroni/picosystem.git ~/picosystem
    mkdir ~/picosystem/build

    cd ~/picosystem/build && \
      cmake -DPICOSYSTEM_DIR:PATH=~/picosystem .. && \
      make

  SHELL
end

(I suspect there are more effective ways to structure this approach; but, it works OK :) )

I’ve been thinking it would be nice to get setup on my Raspberry Pi 400 to really embrace the aesthetic; but, so far haven’t tried that out. How do you like Twister OS? So far, I’ve only run Raspbian on Pis; but, I’m interested in how other OSes work.

-Dan

I love Twister OS, and the Pi is working great for development. I didn’t realize you were using the 32Blit dev path, I’m using C++ but also want to mess with MicroPython and the 32Blit (library?)

MicroPython is also working great on the Pi for PicoSystem development.

Good to hear on Twister OS, I’ll have to try it out!

On the 32Blit sections - I had thought I could use the utilities from that for converting sprites from images to arrays (it might be possible; but, I never figured it out, which is what motivated me to write my own converter). Looks like I should go back over my Vagrantfile and remove the bits I didn’t end up using :)

That said, I also want to try out 32Blit eventually, just wanted to start with the direct C++ library to try and get as sense of how that works before adding any additional layers. Good to know on MicroPython working great - it’s something I’d like to get more comfortable with and try out. I tried a little bit of it with a micro:bit and it seemed like a nice language/ecosystem for coding in.

Yeah, it’s so cool to work on a Pi, it still blows my mind that a sub $100 computer can do so much, I also started with C++ (on the Pi) I did try your converter but couldn’t get it to work on my Pi, I just tested the MicroPython SDK and it worked great (also on the Pi) and do want to look into the 32Blit but it looked real complicated to set up.

The reason I was asking you if you were working on a Pi is when I first went through the “Getting Started with PicoSystem and C++” tutorial from the Pimoroni web site it worked fine (on my Pi) but then my PicoSystem wouldn’t power on properly - sometimes I would have to push the button several times before it would power on. So I went to tech support thinking it was the hardware - they send me a .uf2 that was created with a newer firmware and it seemed to fix it, turns out it was created with a newer version of MicroPython (not C++) so the dev. work that I was doing still had the same problem. When I told them that I was using C++ to develop on a Pi the said that they then had updated the C++ SDK too. but now I can’t follow the “Getting Started with PicoSystem and C++” tutorial from the Pimoroni web site with my Pi to update my SDK, the whole thing works until I try to make (the very last step) then I get all sorts of errors. and so my own project will also fail at this same step. And I’ve told them several times but they don’t seem to understand the actual problem. Crazy thing is it work before :( I have tried the procedure from the web site AND the Github page as well.

So I wanted to see if you were using a Pi as well to see if you had experienced the same issue.

Brian