Help Creating a Space Invaders Game using an RGB WS2812B LED 22x22 sheet

Hi all, I'm trying to create a space invaders game using an RGB WS2812B LED 22x22 sheet (which is really just a strip that a company made into a sheet that zig-zags back and forth). Below is an amazon link to the exact "sheet" of led's I purchased if you need to see that.

I have the character and enemies relatively moving how I want them to, but now I'm stuck trying to figure out how to shoot upwards. I can't just subtract a certain amount of space from the 'top-point' of the spaceship, as the zig-zag of the strip causes that distance to change depending on where the player is.

Is there a way to collect the column of the led matrix so that I can move the led light upwards rather than side to side?

Alternatively..... Is there a way to reverse the order of the matrix of every other line so that my led sheet iterates in a progressive manner, rather than a back-and-forth/zig-zag/snaking pattern

Current Code:

#include <FastLED.h>

#define LED_PIN 7
#define NUM_LEDS 484
#define NUM_ROWS 22
#define NUM_COLS 22

CRGB leds[NUM_LEDS];


unsigned long time;
unsigned long timeMillis;

int buttonValue = A0;
int top = 452;
int left = 472;
int right = 470;
int mid = 471;
int shot;


void setup() {
  // put your setup code here, to run once:
  FastLED.addLeds<WS2812B, LED_PIN, GRB>(leds, NUM_LEDS);


  leds[right] = CRGB(0, 0, 255);
  leds[mid] = CRGB(0, 0, 255);
  leds[left] = CRGB(0, 0, 255);
  leds[top] = CRGB(0, 0, 255);
  FastLED.show();
  Serial.begin(9600);
}

void loop() {
  time = millis() / 1000;
  timeMillis = millis();
  // put your main code here, to run repeatedly:
  int temp = analogRead(buttonValue);
  //Serial.println(temp);


  //Enemy Movement
  if (time % 2 == 0) {
    leds[time] = CRGB(255, 0, 0);
    leds[time + 2] = CRGB(255, 0, 0);
    leds[time + 4] = CRGB(255, 0, 0);
    leds[time + 6] = CRGB(255, 0, 0);
    FastLED.show();

    if (time >= 2) {
      leds[time - 2] = CRGB(0, 0, 0);

      FastLED.show();
    }
  }

  //Player Controls
  if (temp < 100) {
    //do nothing
  } else if (temp < 360 && right <= 484 && top >= 441) {
    //do something
    Serial.println("button left pressed");

    leds[right++] = CRGB(0, 0, 255);
    leds[mid++] = CRGB(0, 0, 255);
    leds[left++] = CRGB(0, 0, 255);
    leds[top--] = CRGB(0, 0, 255);

    leds[right - 2] = CRGB(0, 0, 0);
    leds[top + 2] = CRGB(0, 0, 0);
    FastLED.show();
    delay(400);
  } else if (temp < 460) {
    Serial.println("button shoot pressed");
    shot = top - 25;
    leds[shot] = CRGB(0, 255, 0);

  } else if (temp < 960) {
    Serial.println("button right pressed");
    leds[right--] = CRGB(0, 0, 255);
    leds[mid--] = CRGB(0, 0, 255);
    leds[left--] = CRGB(0, 0, 255);
    leds[top++] = CRGB(0, 0, 255);

    leds[left + 2] = CRGB(0, 0, 0);
    leds[top - 2] = CRGB(0, 0, 0);
    FastLED.show();
    delay(400);
  }
}

I haven't understod how the Neopixels are queued.
if there is some system in how the pixels are connected after each other it must be possible to calculate an offset-value based on XY-coordinates

If the pixels are in no recognisable order you could define a two dimensional array for the XY-coordinates and the values inside each element are the number in the (scrambled) queue

This is a LibreOffice-table with the numbers incrementing horizontally and then down


If you want to move vertically this means you jump up/down 22 positions

Thanks for the reply, but most of the examples use LCD screens, and the ones which don't weren't helpful

Thats what I attempted initially, but the way the "sheet" is set up is a zig-zag. So it goes like this:

1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , 10,11,12,13,14,15,16,17,18,19,20,21,22
44,43,42,41,40,39,38,37,36,35,34,33,32,31,30,29,28,27,26,25,24,23
45,46,47,48,49,50....... etc.

So I cant just subtract 22 from the initial number led where I spawn the shot, as, depending on where the player is, there's a difference in where the shot spawns, and how it moves upwards

Still the same technique applies
This is the order


and this is the "distance" cell 0,0 minus cell 0,1 etc.

And by calculating the "distance" through subtracting cells that are below
you can see the pattern

The hardcore-way is to define a two-dimensional array
which uses XY-coordinates
and the number stored in each element of the array is the number of the zig-zag-order.
This way you can translate XY-coordinates to zig-zag-number

Before updating the display you have to refresh all pixels

The lookup table is a good way to go. Often tables make short work of untangling things.

But… when this is so easily untangled, so regular…

you should be able to do it with simple enough mathematics. I don't think the below is faster or smaller, necessarily, but going all the way with the idea will bend your brain.

It helps to think about it in the abstract.

Use a virtual pixel numbering scheme that goes in lexical order. Increasing on each row left to right.

Use virtual row and column indices if you need to.

Write functions that translate between real and virtual coordinate systems.

For example, using a regular row and column number that is convenient (not serpentine), this function calculates the corresponding real pixel number:

int virtualColmRowToRealN(int virtualColm, int virtualRow)
{
  int realN;

  realN = virtualRow / 2;      // full double rows
  realN = realN * WIDTH * 2;   // add double width

// then fix the column in

  if (virtualRow & 0x1) { // odd rows
    realN += WIDTH * 2 - virtualColm - 1;
  }
  else { // even rows
    realN += virtualColm;
  }

// or use the teneray operator instead of if/else
//  realN += (virtualRow & 0x1) ? ( WIDTH * 2 - virtualColm - 1) : virtualColm;

  return realN;
}


Play with it here.


Code from the wokwi:

//for
// https://forum.arduino.cc/t/help-creating-a-space-invaders-game/1056886
// wokwi: https://wokwi.com/projects/349068891377369684

# define WIDTH  7

int virtualColmRowToRealN(int virtualColm, int virtualRow)
{
  int realN;

  realN = virtualRow / 2;      // full double rows
  realN = realN * WIDTH * 2;   // add double width

// then fix the column in

  if (virtualRow & 0x1) { // odd rows
    realN += WIDTH * 2 - virtualColm - 1;
  }
  else { // even rows
    realN += virtualColm;
  }

// or use the teneray operator instead of if/else
//  realN += (virtualRow & 0x1) ? ( WIDTH * 2 - virtualColm - 1) : virtualColm;

  return realN;
}

void setup() {
  Serial.begin(115200);
  Serial.println("hello maths workd!\n");

  for (int row = 0; row < WIDTH; row++) {
    for (int colm = 0; colm < WIDTH; colm++) {

      Serial.print(virtualColmRowToRealN(colm, row));
      Serial.print("   ");
    }
    Serial.println("");
  }
}

void loop() {
  // put your main code here, to run repeatedly:
}

Similar maths can translate between real colm,row and virtual colm,row and virtual pixel number and so forth.

Do the whole game in a convenient coordinate system, and let the lower level functions sort out the serpentine, and maybe even left<->right or top<->bottom translation(s) as required.

Either by computation or table lookup.

a7

This is the most ghetto way I have ever coded something, but here is how I got my shooting function to work.

distance 1, in the beginning, gathers the top led integer point of my "ship" called 'tp' (for reference this value is 452). Then it subtracts the last LED in integer form from the row above the top point of the ship to get the distance to the end of the row where the top point of the ship is located.

In the code with tp being 452, this would mean distance 1 == 13.

From there, I make that distance negative and subtract it from itself to get the variable position of where the shot should spawn above the ship (if dist1 = 13; substractDist = -26 -------- likewise, if dist1 = 6; subtractDist = -12). After this, I add that negative value to the top point of my ship, which get's the bullet spawn point within a 1 space difference of the top of the ship, if not directly over the top of the ship. Finally, dist2 (and the rest going forward until you reach the top of your matrix) uses the first shot's distance point calculation, and repeats the process of gathering the distance from that point to the edge of the row.

To remedy the issue of the bullent not spawning directly above the ship, part 2 of the code below, shows how I would alternate between adding +1, and not adding anything to the different "ShotDist" calculations (to clarify, all of the "ShotDist" calculations look like part 1 below, there's no difference between them except variable names, and the point at which it starts the calculation from).

From there it's a matter of setting the lights to on or off as they move up the page.

//-------------------- Part 1 of Shooting Code ------------------
  dist1 = (tp - 439);
  subtractDist = -dist1 - dist1;
  firstShotDist = tp + subtractDist;

  dist2 = (firstShotDist - 417);
  subtractDist2 = -dist2 - dist2;
  secondShotDist = firstShotDist + subtractDist2;

  dist3 = (secondShotDist - 395);
  subtractDist3 = -dist3 - dist3;
  thirdShotDist = secondShotDist + subtractDist3;

  dist4 = (thirdShotDist - 373);
  subtractDist4 = -dist4 - dist4;
  fourthShotDist = thirdShotDist + subtractDist4;

//-------------------- Part 2 of Shooting Code ------------------
if (firstShotFired) {
    //delay(1000);
    leds[firstShotDist] = CRGB(0, 0, 0);
    leds[secondShotDist + 1] = CRGB(0, 255, 0);
    secondShot = true;
    FastLED.show();
    firstShotFired = false;

    if (secondShot) {
      //delay(1000);
      leds[secondShotDist + 1] = CRGB(0, 0, 0);
      leds[thirdShotDist] = CRGB(0, 255, 0);
      thirdShot = true;
      FastLED.show();
      secondShot = false;
    }

    if (thirdShot) {
      //delay(1000);
      leds[thirdShotDist] = CRGB(0, 0, 0);
      leds[fourthShotDist + 1] = CRGB(0, 255, 0);
      fourthShot = true;
      FastLED.show();
      thirdShot = false;
    }

again, so, SOOOOOOO ghetto, and incredibly inefficient; but it works and that's what matters. Unfortunately, the way I have my enemies set up does not allow me to properly "kill" them with how the shoot code works, but that's an entirely different matter.