My MKR IoT Carrier game

I'm working on this Breakout clone.

Super cool circular colour display, 3 axis accelerometer, 5 touch buttons and a few more things. Perfect for a game console, right? I bet the Carrier could compete with the Arduboy.

To do:

Check the collision with the 61 cells of the honeycomb. It's so damn small I can't see whether it's pixel perfect or not. Bouncing against the paddle seems to be correct. It's not only about hitting the ball, it's about having the ball hit at the right spot of the paddle, because moving the paddle changes the angle of it due to the circular path.

Add a start screen with settings, scorelist etc.

Add levels. Red cells would need two hits. First hit would turn the cell yellow.

Add flying goodies to be picked.

Add sounds. Add the RGB LEDs just because they are so cool.

Unspaghettify the code and publish it.

Other games, original ideas...

That looks super awesome!

I hope you'll publish your code when it's ready. I literally just received my Opla IoT Kit minutes ago and would definitely have some fun with this game.

Here's the code in its present stage. Not organised at all. Everything happens in setup() while loop() is empty. Just a few functions so far. Most of the stuff I will put in separate functions to get a more readable code. I invite you to test it and comment.

#include <Arduino_MKRIoTCarrier.h>
MKRIoTCarrier carrier;

float p = 3.1415926;

#define EVEN1 65042
#define EVEN2 33284
#define ODD1  65430
#define ODD2  64781
#define ODD3  45831
#define ODD4  6177

#define NOFITEMS 61 // should be 61

#define BLACK    0x0000
#define BLUE     0x001F
#define RED      0xF800
#define GREEN    0x07E0
#define CYAN     0x07FF
#define MAGENTA  0xF81F
#define YELLOW   0xFFE0 
#define WHITE    0xFFFF
#define ORANGE   0b1111101111100000
#define BROWN    0b0111100111100000
#define PINK     0b1111100100000100

// Function prototypes
void newBall(void);
void newGame(void);

// Globals
GFXcanvas16 centre(44, 40);

int ox, oy;
float ballx, bally, oballx, obally;
float vx, vy;
float x, y, z;
int timex, timey, otimex, otimey, otime, newtime;
float a, h, h2, aold, delta;
int lx1, ly1, lx2, ly2;
int lox1, loy1, lox2, loy2;
uint32_t dist2;
float dist;
uint16_t quarantene = 10;
int8_t itemCount;
unsigned long icTime = -1;
unsigned long gameTime;

void setup(void) {
  CARRIER_CASE = false;
  carrier.begin();
  
  newGame();
  
  otimex = 0;
  otimey = 0;

  // Initialize the ball going straight down first time
  ballx = 128;
  bally = 150;
  oballx = 128;
  obally = 150;
  vx = 0.;
  vy = 0.76;

  // Seed the RNG with tilt values from the accelerometer.
  // RNG is used for each new ball direction in newBall()
  while (!carrier.IMUmodule.accelerationAvailable()); 
  carrier.IMUmodule.readAcceleration(x, y, z);
  randomSeed(10000 * (x + y + z));

  ox = carrier.display.width() / 2;
  oy = carrier.display.height() / 2;

  carrier.display.drawRGBBitmap(ox - 21, oy - 19, centre.getBuffer(), 44, 40);
  
  quarantene = 10;
  lox1=0; loy1=0; lox2=1; loy2=1;
  aold = 0;
  carrier.display.setTextSize(2);
  do // main loop
  {
    if (carrier.IMUmodule.accelerationAvailable()) 
    {
      carrier.IMUmodule.readAcceleration(x, y, z);
      h2 = x*x + y*y;
      if (h2 > 0.000001)
      {
        a = atan2(y, x);
        delta = a - aold;
        if (delta < -3.1415926536) delta += 6.28318530718;
        if (delta > 3.1415926536) delta -= 6.28318530718;
        h = sqrt(h2);
        if (h > 1) h = 1;
        aold += h*delta;
        
        
        //carrier.display.fillScreen(0);
        
  
  //      carrier.display.drawCircle(ox+cos(aold)*110, oy-sin(aold)*110, 5, WHITE);
  
        // calculate bat
        lx1 = ox+cos(aold-0.2)*110;
        ly1 = oy-sin(aold-0.2)*110;
        lx2 = ox+cos(aold+0.2)*110;
        ly2 = oy-sin(aold+0.2)*110;
  
        // Draw bat by first drawing over the old bat
        carrier.display.drawLine(lox1, loy1, lox2, loy2, BLACK);
        carrier.display.drawLine(lx1, ly1, lx2, ly2, WHITE);
        lox1 = lx1;
        loy1 = ly1;
        lox2 = lx2;
        loy2 = ly2;
        
        // calculate ball
        ballx += vx;
        bally += vy;
        quarantene++;
        
        dist2 = (ballx - 128)*(ballx - 128)+(bally - 128)*(bally - 128);
        dist = sqrt(dist2);
        if (dist > 129)
        {
          newBall();
        }
        
        // check ball vs. bat
        if ( (quarantene > 5) && (lx1 - ballx)*(ly2 - bally) - (ly1 - bally)*(lx2 - ballx) > 0) // cross product
        {
          // if cross product is positive, we've crossed the bat line
          // if (dist > 128) // just check if we're farther than the screen

  
          if ((lx1 - ballx)*(lx2 - ballx) + (ly1 - bally)*(ly2 - bally) > 0) // if dot product > 0 you missed the bat
          {
            // reset
            newBall();
          }
          else // You hit the bat
          {
            float nx, ny, l, dotp;
            ny = lx2 - lx1;
            nx = ly1 - ly2;
            l = sqrt(nx * nx + ny * ny);
            nx /= l;
            ny /= l;
            dotp = 2. * (vx * nx + vy * ny);
            nx *= dotp;
            ny *= dotp;
            vx -= nx;
            vy -= ny;
            quarantene = 0; // Put ball in quarantene for 5 game loops to avoid stucking
  
          }
        }
        else // Ball is still at centre side of bat line. Check for collision with hex comb.
        {
          uint8_t checkX, checkY; // corner point to check for collision
          float colx, coly;       // mean coordinate for 1 or 2 items the ball colides with
          uint8_t nOfCols; 
           
          if ((ballx > ox - 21) && (ballx < ox + 23) &&
              (bally > oy - 20) && (bally < oy + 20))
          {
            checkX = ballx - ox + 21;
            checkY = bally - oy + 20;
  
            // collisionCheck() returns true if no collision!
            // If false, increase nOfCols and add coordinate of collided item to colX, colY
            // and erase item with black rect
            colx = 0;
            coly = 0;
            nOfCols = 0;
            if (collisionCheck(checkX - 1, checkY - 1, &colx, &coly, &nOfCols))
            {
              if (collisionCheck(checkX - 1, checkY + 1, &colx, &coly, &nOfCols))
              {
                if (collisionCheck(checkX + 1, checkY - 1, &colx, &coly, &nOfCols))
                {
                  collisionCheck(checkX + 1, checkY + 1, &colx, &coly, &nOfCols);
                }
              }
            }
            if (nOfCols) // We have a collision with item
            {
              // Check if all items (61 of them) have been hit
              if (!itemCount)
              {
                delay(1000);
                carrier.display.fillScreen(BLACK);
                carrier.display.setTextColor(PINK);
                carrier.display.setCursor(20, 80);
                carrier.display.setTextSize(3);
                carrier.display.print(" Game\ncompleted\n in ");
                carrier.display.print(newtime);
                carrier.display.print(" s");
                delay(3000);
                carrier.display.fillScreen(BLACK);
                newGame();
                newBall();
              }
              else
              {
                if (nOfCols == 2)
                {
                  colx /= 2;
                  coly /= 2;
                }
                // Now we have a collision between ball and coordinate colX, colY.
                // Do the same as with colliding bat
                
                float nx, ny, l, dotp;
                ny = coly - bally;
                nx = colx - ballx;
                l = sqrt(nx * nx + ny * ny);
                nx /= l;
                ny /= l;
                dotp = 2. * (vx * nx + vy * ny);
                nx *= dotp;
                ny *= dotp;
                vx -= nx;
                vy -= ny;
                carrier.display.drawRGBBitmap(ox - 21, oy - 19, centre.getBuffer(), 44, 40);
              }
            } // End of collision with item
            
          } // End of checking for collision in centre
        }// End of checking if ball inside centre
        
        // Erase old ball
        carrier.display.fillRect(oballx-1, obally-1, 3, 3, BLACK);
        
        // Draw new ball
        carrier.display.drawPixel(ballx, bally, WHITE);
        carrier.display.drawPixel(ballx+1, bally-1, 23275);
        carrier.display.drawPixel(ballx+1, bally+1, 23275);
        carrier.display.drawPixel(ballx-1, bally-1, 23275);
        carrier.display.drawPixel(ballx-1, bally+1, 23275);
        
        carrier.display.drawPixel(ballx-1, bally, 33808);
        carrier.display.drawPixel(ballx+1, bally, 33808);
        carrier.display.drawPixel(ballx, bally-1, 33808);
        carrier.display.drawPixel(ballx, bally-1, 33808);
  
        // Restore ball coordinates
        oballx = ballx;
        obally = bally;
        
  
        // write time
        newtime = (millis() - gameTime) / 1000;
        timex = 118 - (ballx - 128) * 60 / dist;
        timey = 120 - (bally - 128) * 60 / dist;
        
        carrier.display.setTextSize(2);
        carrier.display.setTextColor(BLACK);
        carrier.display.setCursor(otimex, otimey);
        carrier.display.print(otime);
        carrier.display.setTextColor(WHITE);
        carrier.display.setCursor(timex, timey);
        carrier.display.print(newtime);
        otimex = timex;
        otimey = timey;
        otime = newtime;
      }
    }
    if (millis() - icTime > 500)
    {
      carrier.display.setTextColor(BLACK);
      carrier.display.setTextSize(3);
      carrier.display.setCursor(112, 35);
      carrier.display.print(int(itemCount));
      icTime = -1;
      
    }
  }
  while(1);
  
}

bool collisionCheck(uint8_t x, uint8_t y, float *colx, float *coly, uint8_t *nOfCols)
{
  uint8_t fx, fy, mfx, mfy;
  float addx, addy;
  if (centre.getPixel(x, y) == 0)
    return true;
  (*nOfCols)++;
  itemCount--;
  carrier.display.setTextColor(BLACK);
  carrier.display.setTextSize(3);
  carrier.display.setCursor(112, 35);
  carrier.display.print(int(itemCount+1));
  carrier.display.setTextColor(CYAN);
  carrier.display.setTextSize(3);
  carrier.display.setCursor(112, 35);
  carrier.display.print(int(itemCount));
  icTime = millis();
  fy = y / 9;
  mfy = y % 9;
  if (mfy < 4)
  {
    fx = x / 5;
    *coly += 9 * fy + 1.5;  
    *colx += 5 * fx + 1.5;
    centre.fillRect(5 * fx, 9 * fy, 4, 4, BLACK);
  }
  else
  {
    fx = (x - 2) / 5;
    *coly += 9 * fy + 6;
    *colx += 5 * fx + 4;
    centre.fillRect(5 * fx + 2, 9 * fy + 4, 5, 5, BLACK);
  }
  return false;
}

void newBall(void)
{
  carrier.display.setTextSize(2);
  carrier.display.setTextColor(BLACK);
  carrier.display.setCursor(otimex, otimey);
  carrier.display.print(otime);

  carrier.display.setTextSize(3);
  carrier.display.setTextColor(ORANGE);
  carrier.display.setCursor(60, 60);
  carrier.display.print("new ball");
  delay(1000);
  
  carrier.display.setCursor(60, 60);
  carrier.display.setTextColor(BLACK);
  carrier.display.print("new ball");
  
  carrier.display.fillRect(oballx-1, obally-1, 3, 3, BLACK);
  carrier.display.drawLine(lox1, loy1, lox2, loy2, BLACK);
  
#define TIMERX 112
#define TIMERY 35

  for (int8_t i = 3; i > 0; i--)
  {
    carrier.display.setCursor(TIMERX, TIMERY);
    carrier.display.setTextColor(BLACK);
    carrier.display.print(i+1);
    carrier.display.setCursor(TIMERX, TIMERY);
    carrier.display.setTextColor(RED);
    carrier.display.print(i);
    delay(1000);
  }
    carrier.display.setCursor(TIMERX, TIMERY);
    carrier.display.setTextColor(BLACK);
    carrier.display.print(1);
    carrier.display.setCursor(TIMERX, TIMERY);
    carrier.display.setTextColor(RED);
    carrier.display.print(0);
    delay(150);
    carrier.display.setCursor(TIMERX, TIMERY);
    carrier.display.setTextColor(BLACK);
    carrier.display.print(0);

  ballx = 128;
  bally = 150;
  dist = 22.;
  vx = float(random(100)) / 100. - 0.5;
  vy = sqrt(0.5776 - vx * vx);
  quarantene = 5;
  carrier.display.setTextSize(2);
  
}

void newGame(void)
{
  centre.fillScreen(0);

  // Create the honeycomb graphic
  for (int i = 0; i < 5; i++)
  {
    int x0, y0;
    int margin = 2 - i;
    if (margin < 0) margin = -margin;
    for (int j = margin; j < 9-margin; j++)
    {
      x0 = 5*j;
      y0 = 9*i;
      centre.drawPixel(x0 + 1, y0, EVEN2);
      centre.drawPixel(x0 + 2, y0, EVEN2);
      centre.drawPixel(x0 + 1, y0+3, EVEN2);
      centre.drawPixel(x0 + 2, y0+3, EVEN2);
      centre.drawPixel(x0, y0+1, EVEN2);
      centre.drawPixel(x0, y0+2, EVEN2);
      centre.drawPixel(x0 + 3, y0+1, EVEN2);
      centre.drawPixel(x0 + 3, y0+2, EVEN2);

      centre.drawPixel(x0+1, y0+1, EVEN1);
      centre.drawPixel(x0+1, y0+2, EVEN1);
      centre.drawPixel(x0+2, y0+1, EVEN1);
      centre.drawPixel(x0+2, y0+2, EVEN1);
    }
  }
  for (int i = 0; i < 4; i++)
  {
    int x0, y0, margin;
    if (i == 0 || i == 3)
      margin = 1;
    else
      margin = 0;
    for (int j = margin; j < 8-margin; j++)
    {
      x0 = 5*j+3;
      y0 = 9*i+5;
      centre.drawPixel(x0 + 1, y0 - 1, ODD4);
      centre.drawPixel(x0 - 1, y0 + 1, ODD4);
      centre.drawPixel(x0 + 3, y0 + 1, ODD4);
      centre.drawPixel(x0 + 1, y0 + 3, ODD4);

      centre.drawPixel(x0, y0, ODD3);
      centre.drawPixel(x0+2, y0, ODD3);
      centre.drawPixel(x0, y0+2, ODD3);
      centre.drawPixel(x0+2, y0+2, ODD3);

      centre.drawPixel(x0+1, y0, ODD2);
      centre.drawPixel(x0+1, y0+2, ODD2);
      centre.drawPixel(x0, y0+1, ODD2);
      centre.drawPixel(x0+2, y0+1, ODD2);

      centre.drawPixel(x0+1, y0+1, ODD1);
    }
  }
  // Honeycomb graphic ready
  itemCount = NOFITEMS;
  carrier.display.drawRGBBitmap(ox - 21, oy - 19, centre.getBuffer(), 44, 40);
  gameTime = millis();
  oballx = 0;
  obally = 0;
}

void loop()
{
  
}

1 Like

Thanks! That's really a lot of fun. I'm going to have to hook up the battery later on today so I can have a little more freedom of movement without the USB cable's interference.

I participated in an Instructables contest with this game. There I released what I thought was a rather completed game, but it turned out to be very buggy. Still playable, no risk for your hardware. I thought I'd write a proper description of the project on blog.arduino.cc, but seems someone got there before me.
The blog writing links to my instructable. So I guess I'll just continue improving the game and updating the instructable from now on.

I noticed that blog post and it led me to your Instructables project from that. In addition to making a very fun game, you also did an amazing write up! I really enjoyed reading it.

I was glad to see the advancements you made to the game. I had to warn my manager to expect a decrease in my work productivity :wink:.

1 Like