Breakout game w/2.8" TFT-Cap touch screen

EL Wire/Tape/Panels, LEDs, pixels and strips, LCDs and TFTs, etc products from Adafruit

Moderators: adafruit_support_bill, adafruit

Please be positive and constructive with your questions and comments.
Locked
User avatar
krueg
 
Posts: 8
Joined: Sun Apr 07, 2013 10:49 am

Breakout game w/2.8" TFT-Cap touch screen

Post by krueg »

Here's a little breakout game I made with an Arduino Uno and an Adafruit 2.8" TFT Capacitive touch screen.
You'll see the arrays I set up for each level, they take up a lot of memory, I need ideas on a better way to do this.
I have not completed the game over routine so you can play forever with negative lives at the top.
I like the auto play where you can still play if you want. The computer never misses, but will sometimes get stuck in a loop with the ball going straight up and down.

Code: Select all

/***************************************************
  Break Out by Joel Krueger 1-16-2023
  Using an arduino Uno and an
  Adafruit 2.8" TFT Capacitive touch screen sheild
  https://www.adafruit.com/product/1947
****************************************************/

#include <Adafruit_GFX.h>    // Core graphics library
#include <SPI.h>       // this is needed for display
#include <Adafruit_ILI9341.h>
#include <Wire.h>      // this is needed for FT6206
#include <Adafruit_FT6206.h>

// The FT6206 uses hardware I2C (SCL/SDA)
Adafruit_FT6206 ctp = Adafruit_FT6206();

// The display also uses hardware SPI, plus #9 & #10
#define TFT_CS 10
#define TFT_DC 9
Adafruit_ILI9341 tft = Adafruit_ILI9341(TFT_CS, TFT_DC);


int paddleX = 110;
int paddleY = 240;
int paddleLastX = 1;
int paddleW = 40;
int ballX = 110;
int ballY = paddleY;
int ballLastX = ballX;
int ballLastY = ballY;
int ballXDir = 0;
int ballXDirMax = 8;
int ballYDir = -5;
int ballRadius = 3;
unsigned long time;
unsigned long waitUntil;
int ballDelay = 20;
const int bricksTall = 12;
const int bricksWide = 11;
int totalBricks;
int bricksHit = 0;

unsigned int score = 0;
int lives = 3;
int level = 0;
const int highestLevel = 7;

// cycles per second variables
int cycleCount = 0;
int lastCycleCount = cycleCount;
long nextStep = millis() + 1000;

bool count = false;
bool debug = false;
bool autoPlay = false;

byte brick[bricksTall][bricksWide];
byte brickLevels[highestLevel][bricksTall][bricksWide] = {
  {
    {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1},    //Level 1
    {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
    {1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},
    {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
    {1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},
    {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
    {1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},
    {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
    {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
    {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
    {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
    {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
  },
  {
    {1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1},    //Level 2
    {1, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1},
    {1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1},
    {1, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1},
    {1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1},
    {1, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1},
    {1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1},
    {1, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1},
    {1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1},
    {1, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1},
    {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
    {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
  },
  {
    {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1},    //Level 3
    {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
    {1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},
    {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
    {1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},
    {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
    {1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},
    {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
    {1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},
    {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
    {1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},
    {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}
  },
  {
    {0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0},    //Level 4
    {1, 0, 0, 1, 1, 1, 1, 1, 0, 0, 1},
    {1, 1, 0, 0, 1, 1, 1, 0, 0, 1, 1},
    {1, 1, 1, 0, 0, 1, 0, 0, 1, 1, 1},
    {1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1},
    {1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1},
    {1, 0, 0, 1, 1, 1, 1, 1, 0, 0, 1},
    {1, 1, 0, 0, 1, 1, 1, 0, 0, 1, 1},
    {1, 1, 1, 0, 0, 1, 0, 0, 1, 1, 1},
    {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
    {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
    {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
  },
  {
    {1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1},    //Level 5
    {1, 0, 0, 1, 1, 1, 1, 1, 0, 0, 1},
    {1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1},
    {1, 0, 0, 1, 1, 1, 1, 1, 0, 0, 1},
    {1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1},
    {1, 0, 0, 1, 1, 1, 1, 1, 0, 0, 1},
    {1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1},
    {1, 0, 0, 1, 1, 1, 1, 1, 0, 0, 1},
    {1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1},
    {1, 0, 0, 1, 1, 1, 1, 1, 0, 0, 1},
    {1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1},
    {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}
  },
  {
    {1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1},    //Level 6
    {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
    {1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1},
    {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
    {1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1},
    {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
    {1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1},
    {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
    {1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1},
    {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
    {1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1},
    {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}
  },
  {
    {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1},    //Level 7
    {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
    {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
    {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
    {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
    {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
    {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
    {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
    {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
    {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
    {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
    {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}
  },
};

void setup() {
  randomSeed(analogRead(A1));
  while (ballXDir == 0) {
    ballXDir = random(-4, 4);
  }
  if (debug) {
    Serial.begin(9600);
  }
  tft.begin();
  ctp.begin(40);
  autoplayChoice();
  loadBricks();
  newScreen();
}

void loop() {
  if (count) {          //cycles per second routine
    cycleCount++;
    if (millis() > nextStep) {
      //Serial.print("Cycles per second = ");
      //Serial.println(cycleCount);
      showCycles();
      cycleCount = 0;
      nextStep = millis() + 1000;
    }
  }

  paddle();
  time = millis();
  if (time > waitUntil) {
    waitUntil = time + ballDelay;
    moveBall();
  }
  if (autoPlay) {
    paddleX = ballX - (paddleW / 2);
  }
}

void moveBall() {
  //check if the ball hits the side walls
  if (ballX < ballXDir * -1 + ballRadius + 1 | ballX > 238 - ballXDir - ballRadius) {
    ballXDir = -ballXDir;
  }
  //check if the ball hits the top of the screen
  if (ballY < ballYDir * -1 + ballRadius + 11) {
    ballYDir = -ballYDir;
  }
  //  /******************************************************
  //check if ball hits bottom of bricks
  for (int y = 0; y < bricksTall; y++) {
    for (int x = 0; x < bricksWide; x++) {
      if (brick[y][x] == 1) {
        if (ballX > x * 20 + 10 - ballRadius & ballX < x * 20 + 29 + ballRadius
            & ballY > y * 10 + 25 - ballRadius & ballY < y * 10 + 34 + ballRadius) {
          tft.fillRect(x * 20 + 10, y * 10 + 25, 19, 9, ILI9341_BLACK);
          brick[y][x] = 0;
          if ((ballX < x * 20 + 10 & ballXDir > 0) | (ballX > x * 20 + 29 & ballXDir < 0)) {
            ballXDir = -ballXDir;
          } else {
            ballYDir = -ballYDir;
          }
          bricksHit ++;
          score = score + 10;
          showScore();
          break;
        }
      }
    }
  }
  //  *********************************************************/

  //check if the ball hits the paddle
  if (ballX > paddleX - ballXDir - ballRadius & ballX < paddleX + paddleW + ballXDir * -1 + ballRadius
      & ballY > paddleY - ballYDir - ballRadius & ballY < paddleY + 4 + ballRadius) {
    ballXDir = ballXDir - (((paddleX + paddleW / 2) - ballX) * .3);
    if (ballXDir < -ballXDirMax) {
      ballXDir = -ballXDirMax;
    }
    else if (ballXDir > ballXDirMax) {
      ballXDir = ballXDirMax;
    }
    ballYDir = -ballYDir;
  }
  //check if the ball went past the paddle
  if (ballY > paddleY + 10) {
    for (int i = 0; i < 10; i++) {
      tft.setTextColor(ILI9341_YELLOW);  tft.setTextSize(6);
      tft.setCursor(50, 120);
      tft.print("BANNED");
      tft.setTextColor(ILI9341_RED);
      tft.setCursor(50, 120);
      tft.print("BANNED");
    }
    delay(1000);
    lives--;
    paddleLastX = 1;
    newScreen();
    ballY = paddleY;
    ballLastY = ballY;
    ballYDir = -5;
    ballXDir = 4;
    //bricksHit = 0;
    //score = 0;
  }
  if (bricksHit == totalBricks) {
    tft.fillCircle(ballX, ballY, ballRadius, ILI9341_BLACK);
    tft.setTextColor(ILI9341_CYAN);  tft.setTextSize(4);
    tft.setCursor(30, 60);
    tft.print("LEVEL " + String(level + 1));
    tft.setTextColor(ILI9341_CYAN);  tft.setTextSize(4);
    tft.setCursor(20, 100);
    tft.print("COMPLETE");
    delay(2000);
    level++;
    if (level == highestLevel) {
      level = 0;
    }
    ballDelay--;
    loadBricks();
    newScreen();
    ballY = paddleY;
    ballLastY = ballY;
    ballYDir = -5;
    ballXDir = 4;
    bricksHit = 0;
  }
  ballX = ballX + ballXDir; //move the ball x
  ballY = ballY + ballYDir; //move the ball y
  tft.fillCircle(ballLastX, ballLastY, ballRadius, ILI9341_BLACK); //erase the old ball
  tft.fillCircle(ballX, ballY, ballRadius, ILI9341_WHITE); // draw the new ball
  ballLastX = ballX;
  ballLastY = ballY;
}

void paddle() {
  if (ctp.touched()) {
    TS_Point p = ctp.getPoint();    // Retrieve a point
    paddleX = map(p.x, 50, 189, 239, 1) - paddleW / 2;
  }
  if (paddleX < 1) { //dont let the paddle go to far left
    paddleX = 1;
  } else if (paddleX > 239 - paddleW) { //dont let the paddle go to far right
    paddleX = 239 - paddleW;
  }
  if (paddleLastX != paddleX) {
    tft.fillRect(paddleLastX, paddleY, paddleW, 4, ILI9341_BLACK); //erase the old paddle
    tft.fillRect(paddleX, paddleY, paddleW, 4, ILI9341_CYAN);  //draw the new paddle
    paddleLastX = paddleX;
  }
}

void newScreen() {
  showBorder();
  scoreBoard();
  showLevel();
  showLives();
  showScore();
  paddle();
  showBricks();
  if (! autoPlay) {
    waitForUser();
  }
}

void showBorder() {
  tft.fillScreen(ILI9341_BLACK);
  tft.drawFastVLine(0, 10, paddleY, ILI9341_YELLOW);
  tft.drawFastHLine(0, 10, 239, ILI9341_YELLOW);
  tft.drawFastVLine(239, 10, paddleY, ILI9341_YELLOW);
  for (int i = 3; i < 240; i = i + 10) {
    tft.drawFastHLine(i, paddleY + 10, 5, ILI9341_RED);
  }
}

void scoreBoard() {
  tft.setTextColor(ILI9341_WHITE, ILI9341_BLACK);  tft.setTextSize(1);
  tft.setCursor(10, 0);
  tft.print("Level");
  tft.setCursor(90, 0);
  tft.print("Lives");
  tft.setCursor(160, 0);
  tft.print("Score");
}

void showLevel() {
  tft.setCursor(50, 0);
  //tft.fillRect(50, 0, 18, 7, ILI9341_BLACK);
  tft.print(String (level + 1));
}

void showLives() {
  tft.setCursor(130, 0);
  //tft.fillRect(130, 0, 18, 7, ILI9341_BLACK);
  tft.print(String (lives));
}

void showScore() {
  tft.setCursor(200, 0);
  //tft.fillRect(200, 0, 30, 7, ILI9341_BLACK);
  tft.print(String (score));
}

void showCycles() {
  tft.setTextColor(ILI9341_WHITE, ILI9341_BLACK);  tft.setTextSize(1);
  tft.setCursor(5, 300);
  //tft.fillRect(5, 300, 25, 8, ILI9341_BLACK);
  tft.print(String (cycleCount)+"   ");
}

void showBricks() {
  for (int y = 0; y < bricksTall; y++) {
    for (int x = 0; x < bricksWide; x++) {
      if (brick[y][x] == 1) {
        tft.fillRoundRect(x * 20 + 10, y * 10 + 25, 19, 9, 3, y * 4630 + 1630); //65535/12=5461 colors
        tft.fillRoundRect(x * 20 + 14, y * 10 + 28, 11, 3, 1, y * 4630 + 6260); //65535/12=5461 colors
      }
      if (debug) {
        Serial.print(level);
        Serial.print("  ");
        Serial.print(y);
        Serial.print("  ");
        Serial.print(x);
        Serial.print("  ");
        Serial.print(brick[y][x]);
        Serial.print("  ");
        Serial.print(brickLevels[level][y][x]);
        Serial.print("  ");
        Serial.println(totalBricks);
      }
    }
  }
}

void loadBricks() {
  totalBricks = 0;
  for (int y = 0; y < bricksTall; y++) {
    for (int x = 0; x < bricksWide; x++) {
      brick[y][x] = brickLevels[level][y][x];
      if (brick[y][x] == 1) {
        totalBricks++;
      }
    }
  }
}

void waitForUser() {
  tft.setTextColor(ILI9341_YELLOW);  tft.setTextSize(2);
  tft.setCursor(30, 280);
  tft.print("TAP TO CONTINUE");
  while (! ctp.touched()) {
    //waiting for touch screen to be pressed
  }
  tft.setTextColor(ILI9341_BLACK);
  tft.setCursor(30, 280);
  tft.print("TAP TO CONTINUE");
  tft.setTextColor(ILI9341_WHITE);  tft.setTextSize(1);
}

void autoplayChoice() {
  showBorder();
  scoreBoard();
  showLevel();
  showLives();
  showScore();
  paddle();
  tft.fillRoundRect(25, 40, 190, 60, 9, ILI9341_BLUE); //65535/12=5461 colors
  tft.drawRoundRect(25, 40, 190, 60, 9, ILI9341_WHITE); //65535/12=5461 colors
  tft.fillRoundRect(25, 140, 190, 60, 9, ILI9341_BLUE); //65535/12=5461 colors
  tft.drawRoundRect(25, 140, 190, 60, 9, ILI9341_WHITE); //65535/12=5461 colors

  tft.setTextColor(ILI9341_GREEN);  tft.setTextSize(3);
  tft.setCursor(40, 60);
  tft.print("Play Game");

  tft.setTextColor(ILI9341_MAGENTA);
  tft.setCursor(40, 160);
  tft.print("Auto Play");

  tft.setTextColor(ILI9341_YELLOW);
  tft.setCursor(50, 280);
  tft.print("BREAKOUT");
  
  while (true) {

    if (ctp.touched()) {
      TS_Point p = ctp.getPoint();    // Retrieve a point
      int XPOS = map(p.x, 10, 230, 239, 0);
      int YPOS = map(p.y, 10, 310, 319, 0);
      if (XPOS > 25 & XPOS < 215) {
        if (YPOS > 40 & YPOS < 100) {
          return;
        } else if (YPOS > 140 & YPOS < 200) {
          autoPlay = true;
          return;
        }
      }

    }
  }
}

User avatar
blnkjns
 
Posts: 963
Joined: Fri Oct 02, 2020 3:33 am

Re: Breakout game w/2.8" TFT-Cap touch screen

Post by blnkjns »

You waste a lot of memory on the tables. The values are just 0 and 1, so use a boolean. You can also write them down as bits:

Code: Select all

byte brickLevels[highestLevel][bricksTall][(bricksWide+7)/8] = {
  {
    {B11111111,B11100000},    //Level 1
Reduction is 82%
If you count the hits where the paddle remains at the same position, you can have it shift slightly after 3 vertical hits. That way it gets loose again.

User avatar
adafruit_support_mike
 
Posts: 67485
Joined: Thu Feb 11, 2010 2:51 pm

Re: Breakout game w/2.8" TFT-Cap touch screen

Post by adafruit_support_mike »

Reducing the level patterns to bit vectors.. each row being a uint16_t integer, where the individual bits are blocks on the screen.. will save memory.

If you use that approach, do yourself a favor and write a layer of code that takes integer-block-location values and converts them to bit positions in the uint16_t bit vectors. It's a little more work up front, but will save you a huge amount of hard-to-read bit twiddling in your game code.


For the auto-play mode, you don't have to position the center of the paddle at the intercept point every time. Adding a random offset so the ball hits somewhere in the center 80% of the paddle will still let the code play forever, but will make the play more interesting to watch.

User avatar
krueg
 
Posts: 8
Joined: Sun Apr 07, 2013 10:49 am

Re: Breakout game w/2.8" TFT-Cap touch screen

Post by krueg »

Great advice guys, I will have to do some learning with working at the bit level to make this work. I see the benefits on saving memory this way, time to find some examples and get the code put together.

Thanks guys!

User avatar
blnkjns
 
Posts: 963
Joined: Fri Oct 02, 2020 3:33 am

Re: Breakout game w/2.8" TFT-Cap touch screen

Post by blnkjns »

Easiest to grasp is the commands bitRead, bitWrite and bitSet

User avatar
krueg
 
Posts: 8
Joined: Sun Apr 07, 2013 10:49 am

Re: Breakout game w/2.8" TFT-Cap touch screen

Post by krueg »

Here's what I ended up with, I went with 16 bit ints for each row using the 11 right most bits for the blocks. I only applied this to the levels and the routine that loads the levels into the array used during game play. I could go further with this optimizing but I'll save that for later. It still saves a bunch of memory and can handle a bunch more levels. I also took your advice for avoiding getting stuck in a loop by adding this little snippet when the ball hits the paddle.

Code: Select all

    if (autoPlay) {
      ballXDir += random(-1, 1);
      while (ballXDir == 0) {
        ballXDir = random(-1, 1);
      }
    }
Here's the full code.

Code: Select all

/***************************************************
  Break Out by Joel Krueger 1-16-2023
  Using an arduino Uno and an
  Adafruit 2.8" TFT Capacitive touch screen sheild
  https://www.adafruit.com/product/1947
****************************************************/

#include <Adafruit_GFX.h>    // Core graphics library
#include <SPI.h>       // this is needed for display
#include <Adafruit_ILI9341.h>
#include <Wire.h>      // this is needed for FT6206
#include <Adafruit_FT6206.h>

// The FT6206 uses hardware I2C (SCL/SDA)
Adafruit_FT6206 ctp = Adafruit_FT6206();

// The display also uses hardware SPI, plus #9 & #10
#define TFT_CS 10
#define TFT_DC 9
Adafruit_ILI9341 tft = Adafruit_ILI9341(TFT_CS, TFT_DC);


int paddleX = 110;
int paddleY = 240;
int paddleLastX = 1;
int paddleW = 40;
int ballX = 110;
int ballY = paddleY;
int ballLastX = ballX;
int ballLastY = ballY;
int ballXDir = 0;
int ballXDirMaxStart = 5;
int ballXDirMax = ballXDirMaxStart;
int ballYDir = -5;
int ballRadius = 3;
unsigned long time;
unsigned long waitUntil;
int ballDelayStart = 25;        //Starting speed of the ball for a new game
int ballDelay = ballDelayStart; //This is decreased when you finish a level during the game
int totalBricks;                //Number of bricks for each level
int bricksHit = 0;              //Count of bricks hit, when it equals totalBricks the level is completed

unsigned int score = 0;
const int maxLives = 5;         //Max lives for starting over
int lives = maxLives;           //Number of lives left during the game
int level = 0;

// cycles per second variables
int cycleCount = 0;
int lastCycleCount = cycleCount;
long nextStep = millis() + 1000;

bool count = false;             //Shows how many cycles per second on the screen
bool debug = false;             //Shows info on the screen when trying to debug code
bool autoPlay = false;

const int highestLevel = 12;    //These must match the arrays below if you create new levels
const int bricksTall = 12;
const int bricksWide = 11;

byte brick[bricksTall][bricksWide];

int brickLevels[highestLevel][bricksTall] = {
  {
    0b11111111111,      //Level 1
    0b10000000001,
    0b11111111111,
    0b10000000001,
    0b11111111111,
    0b10000000001,
    0b11111111111,
    0b00000000000,
    0b00000000000,
    0b00000000000,
    0b00000000000,
    0b00000000000
  },
  {
    0b11111111111,      //Level 1a
    0b10010101001,
    0b11111111111,
    0b10010101001,
    0b11111111111,
    0b10010101001,
    0b11111111111,
    0b00000000000,
    0b00000000000,
    0b00000000000,
    0b00000000000,
    0b00000000000
  },
  {
    0b10101010101,      //Level poles
    0b10101010101,
    0b10101010101,
    0b10101010101,
    0b10101010101,
    0b10101010101,
    0b10101010101,
    0b10101010101,
    0b00000000000,
    0b00000000000,
    0b00000000000,
    0b00000000000
  },
  {
    0b11011011011,      //Level double poles
    0b11011011011,
    0b11011011011,
    0b11011011011,
    0b11011011011,
    0b11011011011,
    0b11011011011,
    0b11011011011,
    0b00000000000,
    0b00000000000,
    0b00000000000,
    0b00000000000
  },
  {
    0b01010101010,    //level 2b
    0b10101010101,
    0b01010101010,
    0b10101010101,
    0b01010101010,
    0b10101010101,
    0b11111111111,
    0b11111111111,
    0b00000000000,
    0b00000000000,
    0b00000000000,
    0b00000000000
  },
  {
    0b11111111111,    //level 2c
    0b11111111111,
    0b11111111111,
    0b10101010101,
    0b01010101010,
    0b11111111111,
    0b11111111111,
    0b11111111111,
    0b00000000000,
    0b00000000000,
    0b00000000000,
    0b00000000000
  },
  {
    0b11111111111,      //Level inverse V
    0b10111111101,
    0b10011111001,
    0b11001110011,
    0b11100100111,
    0b10110001101,
    0b10111011101,
    0b11111111111,
    0b00000000000,
    0b00000000000,
    0b00000000000,
    0b00000000000
  },
  {
    0b11111111111,      //Level up side down V
    0b10111011101,
    0b10110001101,
    0b11100100111,
    0b11001110011,
    0b10011111001,
    0b10111111101,
    0b11111111111,
    0b00000000000,
    0b00000000000,
    0b00000000000,
    0b00000000000
  },
  {
    0b11100000111,      //Level X
    0b11110001111,
    0b01111011110,
    0b00111111100,
    0b00011111000,
    0b00111111100,
    0b01111011110,
    0b11110001111,
    0b11100000111,
    0b00000000000,
    0b00000000000,
    0b00000000000
  },
  {
    0b00011111000,      //Level oval
    0b00111111100,
    0b01111011110,
    0b11110001111,
    0b11100000111,
    0b11110001111,
    0b01111011110,
    0b00111111100,
    0b00011111000,
    0b00000000000,
    0b00000000000,
    0b00000000000
  },
  {
    0b00000100000,      //Level star
    0b00001110000,
    0b00011111000,
    0b11111111111,
    0b01111111110,
    0b00111111100,
    0b00111011100,
    0b01110001110,
    0b01100000110,
    0b11000000011,
    0b10000000001,
    0b00000000000
  },
  {
    0b11111111111,      //Level top
    0b11111111111,
    0b11111111111,
    0b11111111111,
    0b11111111111,
    0b11111111111,
    0b11111111111,
    0b11111111111,
    0b11111111111,
    0b11111111111,
    0b11111111111,
    0b11111111111
  }
};

void setup() {
  randomSeed(analogRead(A1));
  while (ballXDir == 0) {
    ballXDir = random(-4, 4);
  }
  tft.begin();
  ctp.begin(40);
  autoplayChoice();
  loadBricks();
  newScreen();
}

void loop() {
  if (count) {          //cycles per second routine
    cycleCount++;
    if (millis() > nextStep) {
      showCycles();
      cycleCount = 0;
      nextStep = millis() + 1000;
    }
  }

  paddle();
  time = millis();
  if (time > waitUntil) {
    waitUntil = time + ballDelay;
    moveBall();
  }
  if (autoPlay) {
    paddleX = ballX - (paddleW / 2);
  }
}

void moveBall() {
  //check if the ball hits the side walls
  if (ballX < ballXDir * -1 + ballRadius + 1 | ballX > 238 - ballXDir - ballRadius) {
    ballXDir = -ballXDir;
  }
  //check if the ball hits the top of the screen
  if (ballY < ballYDir * -1 + ballRadius + 11) {
    ballYDir = -ballYDir;
  }
  //  /******************************************************
  //check if ball hits bottom of bricks
  for (int y = 0; y < bricksTall; y++) {
    for (int x = 0; x < bricksWide; x++) {
      if (brick[y][x] == 1) {
        if (ballX > x * 20 + 10 - ballRadius & ballX < x * 20 + 29 + ballRadius
            & ballY > y * 10 + 25 - ballRadius & ballY < y * 10 + 34 + ballRadius) {
          tft.fillRect(x * 20 + 10, y * 10 + 25, 19, 9, ILI9341_BLACK);
          brick[y][x] = 0;
          if ((ballX < x * 20 + 10 & ballXDir > 0) | (ballX > x * 20 + 29 & ballXDir < 0)) {
            ballXDir = -ballXDir;
          } else {
            ballYDir = -ballYDir;
          }
          bricksHit ++;
          score = score + 10;
          showScore();
          break;
        }
      }
    }
  }
  //  *********************************************************/

  //check if the ball hits the paddle
  if (ballX > paddleX - ballXDir - ballRadius & ballX < paddleX + paddleW + ballXDir * -1 + ballRadius
      & ballY > paddleY - ballYDir - ballRadius & ballY < paddleY + 4 + ballRadius) {
    ballXDir = ballXDir - (((paddleX + paddleW / 2) - ballX) * .3);
    if (autoPlay) {
      ballXDir += random(-1, 1);
      while (ballXDir == 0) {
        ballXDir = random(-1, 1);
      }
    }
    if (ballXDir < -ballXDirMax) {
      ballXDir = -ballXDirMax;
    }
    else if (ballXDir > ballXDirMax) {
      ballXDir = ballXDirMax;
    }
    ballYDir = -ballYDir;
  }
  //check if the ball went past the paddle
  if (ballY > paddleY + 10) {
    for (int i = 0; i < 10; i++) {
      tft.setTextColor(ILI9341_YELLOW, ILI9341_BLACK);  tft.setTextSize(6);
      tft.setCursor(50, 120);
      tft.print("OOPS");
      tft.setTextColor(ILI9341_RED);
      tft.setCursor(50, 120);
      tft.print("OOPS");
    }
    delay(1000);
    lives--;
    if (lives < 1) {
      tft.setTextColor(ILI9341_WHITE, ILI9341_BLACK);  tft.setTextSize(1);
      showLives();
      gameOver();
    }
    paddleLastX = 1;
    newScreen();
    ballY = paddleY;
    ballLastY = ballY;
    ballYDir = -5;
    ballXDir = 4;
    //bricksHit = 0;
    //score = 0;
  }
  if (bricksHit == totalBricks) {
    tft.fillCircle(ballX, ballY, ballRadius, ILI9341_BLACK);
    tft.setTextColor(ILI9341_CYAN);  tft.setTextSize(4);
    tft.setCursor(30, 60);
    tft.print("LEVEL " + String(level + 1));
    tft.setTextColor(ILI9341_CYAN);  tft.setTextSize(4);
    tft.setCursor(20, 100);
    tft.print("COMPLETE");
    delay(2000);
    level++;
    if (level == highestLevel) {
      level = 0;
      ballXDirMax++;
    }
    if (level % 2 == 0) {
      ballDelay--;
    }
    loadBricks();
    newScreen();
    ballY = paddleY;
    ballLastY = ballY;
    ballYDir = -5;
    ballXDir = 4;
    bricksHit = 0;
  }
  ballX = ballX + ballXDir; //move the ball x
  ballY = ballY + ballYDir; //move the ball y
  tft.fillCircle(ballLastX, ballLastY, ballRadius, ILI9341_BLACK); //erase the old ball
  tft.fillCircle(ballX, ballY, ballRadius, ILI9341_WHITE); // draw the new ball
  ballLastX = ballX;
  ballLastY = ballY;
}

void paddle() {
  if (ctp.touched()) {
    TS_Point p = ctp.getPoint();    // Retrieve a point
    paddleX = map(p.x, 50, 189, 239, 1) - paddleW / 2;
    if (p.y < 15) {
      tft.drawFastHLine(0, paddleY + 12, 239, ILI9341_RED);
    }
    else {
      tft.drawFastHLine(0, paddleY + 12, 239, ILI9341_BLACK);
    }
  }
  if (paddleX < 1) { //dont let the paddle go to far left
    paddleX = 1;
  } else if (paddleX > 239 - paddleW) { //dont let the paddle go to far right
    paddleX = 239 - paddleW;
  }
  if (paddleLastX != paddleX) {
    tft.fillRect(paddleLastX, paddleY, paddleW, 4, ILI9341_BLACK); //erase the old paddle
    tft.fillRect(paddleX, paddleY, paddleW, 4, ILI9341_CYAN);  //draw the new paddle
    paddleLastX = paddleX;
  }
}

void newScreen() {
  showBorder();
  scoreBoard();
  showLevel();
  showLives();
  showScore();
  paddle();
  showBricks();
  if (! autoPlay) {
    waitForUser();
  }
}

void showBorder() {
  tft.fillScreen(ILI9341_BLACK);
  tft.drawFastVLine(0, 10, paddleY, ILI9341_YELLOW);
  tft.drawFastHLine(0, 10, 239, ILI9341_YELLOW);
  tft.drawFastVLine(239, 10, paddleY, ILI9341_YELLOW);
  for (int i = 3; i < 240; i = i + 10) {
    tft.drawFastHLine(i, paddleY + 10, 5, ILI9341_RED);
  }
}

void scoreBoard() {
  tft.setTextColor(ILI9341_WHITE, ILI9341_BLACK);  tft.setTextSize(1);
  tft.setCursor(10, 0);
  tft.print("Level");
  tft.setCursor(90, 0);
  tft.print("Lives");
  tft.setCursor(160, 0);
  tft.print("Score");
}

void showLevel() {
  tft.setCursor(50, 0);
  tft.print(String (level + 1));
}

void showLives() {
  tft.setCursor(130, 0);
  tft.print(String (lives));
}

void showScore() {
  tft.setTextColor(ILI9341_WHITE, ILI9341_BLACK);  tft.setTextSize(1);
  tft.setCursor(200, 0);
  tft.print(String (score));
}

void showCycles() {
  tft.setTextColor(ILI9341_WHITE, ILI9341_BLACK);  tft.setTextSize(1);
  tft.setCursor(5, 300);
  tft.print(String (cycleCount) + "   ");
}

void showBricks() {
  for (int y = 0; y < bricksTall; y++) {
    for (int x = 0; x < bricksWide; x++) {
      if (brick[y][x] == 1) {
        tft.fillRoundRect(x * 20 + 10, y * 10 + 25, 19, 9, 3, y * 4630 + 1630); //65535/12=5461 colors
        tft.fillRoundRect(x * 20 + 14, y * 10 + 28, 11, 3, 1, y * 4630 + 6260); //65535/12=5461 colors
        //tft.fillRoundRect(x * 20 + 10, y * 10 + 25, 19, 9, 3, random(0xFFFF)); //65535/12=5461 colors
        //tft.fillRoundRect(x * 20 + 14, y * 10 + 28, 11, 3, 1, random(0xFFFF)); //65535/12=5461 colors
      }
    }
  }
  if (debug) {
    tft.setTextColor(ILI9341_WHITE, ILI9341_BLACK);  tft.setTextSize(1);
    tft.setCursor(5, 280);
    tft.print(String (ballDelay) + "   ");
  }
}

void loadBricks() {
  totalBricks = 0;
  for (int y = 0; y < bricksTall; y++) {
    for (int x = 0; x < bricksWide; x++) {
      if (brickLevels[level][y] & (1 << 10 - x)) {
        brick[y][x] = 1;
        totalBricks++;
      }
      else {
        brick[y][x] = 0;
      }
    }
  }
}

void waitForUser() {
  while (! ctp.touched()) {
    //waiting for touch screen to be pressed
    tft.setTextColor(ILI9341_YELLOW, ILI9341_BLACK);  tft.setTextSize(2);
    tft.setCursor(30, 280);
    tft.print("TAP TO CONTINUE");
    delay(100);
    tft.setTextColor(ILI9341_RED, ILI9341_BLACK);
    tft.setCursor(30, 280);
    tft.print("TAP TO CONTINUE");
    delay(100);
  }
  tft.setTextColor(ILI9341_BLACK, ILI9341_BLACK);
  tft.setCursor(30, 280);
  tft.print("TAP TO CONTINUE");
  tft.setTextColor(ILI9341_WHITE);  tft.setTextSize(1);
}

void autoplayChoice() {
  showBorder();
  scoreBoard();
  showLevel();
  showLives();
  showScore();
  paddle();
  tft.fillRoundRect(25, 40, 190, 60, 9, ILI9341_BLUE); //65535/12=5461 colors
  tft.drawRoundRect(25, 40, 190, 60, 9, ILI9341_WHITE); //65535/12=5461 colors
  tft.fillRoundRect(25, 140, 190, 60, 9, ILI9341_BLUE); //65535/12=5461 colors
  tft.drawRoundRect(25, 140, 190, 60, 9, ILI9341_WHITE); //65535/12=5461 colors
  tft.fillRoundRect(25, 270, 190, 40, 20, ILI9341_BLUE); //65535/12=5461 colors
  tft.drawRoundRect(25, 270, 190, 40, 20, ILI9341_WHITE); //65535/12=5461 colors
  //tft.fillRoundRect(40, 285, 160, 10, 4, ILI9341_CYAN); //65535/12=5461 colors

  tft.setTextColor(ILI9341_GREEN);  tft.setTextSize(3);
  tft.setCursor(40, 60);
  tft.print("Play Game");

  tft.setTextColor(ILI9341_MAGENTA);
  tft.setCursor(40, 160);
  tft.print("Auto Play");

  tft.setTextColor(ILI9341_YELLOW);
  tft.setCursor(50, 280);
  tft.print("BREAKOUT");

  while (true) {

    if (ctp.touched()) {
      TS_Point p = ctp.getPoint();    // Retrieve a point
      int XPOS = map(p.x, 10, 230, 239, 0);
      int YPOS = map(p.y, 10, 310, 319, 0);
      if (XPOS > 25 & XPOS < 215) {
        if (YPOS > 40 & YPOS < 100) {
          return;
        } else if (YPOS > 140 & YPOS < 200) {
          autoPlay = true;
          return;
        }
      }

    }
  }
}

void gameOver() {
  for (int i = 0; i < 5; i++) {
    tft.setTextColor(ILI9341_YELLOW);  tft.setTextSize(4);
    tft.setCursor(15, 190);
    tft.print("GAME OVER");
    delay(500);
    tft.setTextColor(ILI9341_RED);
    tft.setCursor(15, 190);
    tft.print("GAME OVER");
    delay(500);
  }
  waitForUser();
  ballDelay = ballDelayStart;
  ballXDirMax = ballXDirMaxStart;
  bricksHit = 0;
  score = 0;
  lives = maxLives;
  level = 0;
  loadBricks();
  autoplayChoice();
}

User avatar
adafruit_support_mike
 
Posts: 67485
Joined: Thu Feb 11, 2010 2:51 pm

Re: Breakout game w/2.8" TFT-Cap touch screen

Post by adafruit_support_mike »

Looks good, and remember Don Knuth's rules of optimization:

1: Don't.
2: Don't, yet.

Premature attempts to optimize code have done more harm than all the optimization in history has paid back, and repeated testing shows that even the most experienced programmers have a pathetically bad record of choosing good candidates for optimization.

Basically, no one can optimize code without profiling the heck out of it first. Hot loops occur in places programmers would never expect, and far too many pieces of elegantly tuned code have almost no effect on program timing.

Locked
Please be positive and constructive with your questions and comments.

Return to “Glowy things (LCD, LED, TFT, EL) purchased at Adafruit”