Hackster is hosting Hackster Holidays, Ep. 7: Livestream & Giveaway Drawing. Watch previous episodes or stream live on Friday!Stream Hackster Holidays, Ep. 7 on Friday!
John Bradnam
Published © GPL3+

Breakout Game

Breakout game for the Matrix Game Console. Features two variants of the game and also has built-in auto-play.

IntermediateFull instructions provided12 hours168
Breakout Game

Things used in this project

Hardware components

Matrix Game Console
See https://www.hackster.io/john-bradnam/matrix-game-console-470ad2 for build instructions
×1

Software apps and online services

Arduino IDE
Arduino IDE

Story

Read more

Schematics

Schematic

Code

BreakoutV1.ino

C/C++
/**************************************************************************
 Breakout V1
 
 Author: John Bradnam (jbrad2089@gmail.com)
 
 2022-03-25
  Create program for ATtiny3216
  - Button A - Play with bounce off blocks
  - Button B - Play without bounce off blocks (easier)
  - Button C - Auto play. Also terminates any active game

  - Ball speeds up each time screen is cleared of blocks
  - Each block scores a point
  - Bonus 50 points for clearing all blocks
  - 5 lives per game
  - Extra life given each time the boaard is cleared in a game
  - Bat is made up of three cells. 
    Left cell moves ball one cell left
    Middle cell doesn't move ball (slightly higher note played)
    Right cell moves ball one cell right
  
 --------------------------------------------------------------------------
 Arduino IDE:
 --------------------------------------------------------------------------
  BOARD: 20pin tinyAVR 0/1/2 Series
  Chip: ATtiny3216
  Clock Speed: 20MHz
  millis()/micros() Timer: "TCD0 (1-series only, default there)"
  Programmer: jtag2updi (megaTinyCore)

  ATtiny3216 Pins mapped to Ardunio Pins
                            _____
                    VDD   1|*    |20  GND
   (nSS)  (AIN4) PA4  0~  2|     |19  16~ PA3 (AIN3)(SCK)(EXTCLK)
          (AIN5) PA5  1~  3|     |18  15  PA2 (AIN2)(MISO)
   (DAC)  (AIN6) PA6  2   4|     |17  14  PA1 (AIN1)(MOSI)
          (AIN7) PA7  3   5|     |16  17  PA0 (AIN0/nRESET/UPDI)
          (AIN8) PB5  4   6|     |15  13  PC3
          (AIN9) PB4  5   7|     |14  12  PC2
   (RXD) (TOSC1) PB3  6   8|     |13  11~ PC1 (PWM only on 1-series)
   (TXD) (TOSC2) PB2  7~  9|     |12  10~ PC0 (PWM only on 1-series)
   (SDA) (AIN10) PB1  8~ 10|_____|11   9~ PB0 (AIN11)(SCL)
 

  PA0 to PA7, PB0 to PB5, PC0 to PC3 can be analog or digital
  PWM on D0, D1, D7, D8, D9, D10, D11, D16
  
 **************************************************************************/

//Display functions to drive RED/GREEN matrix
#include "Display.h"
#include "Sound.h"

#define SW_BA 9        //PB0
#define SW_BB 10       //PC0
#define SW_BC 11       //PC1
#define SW_LF 3        //PA7
#define SW_RG 2        //PA6
#define SW_UP 1        //PA5
#define SW_DN 0        //PA4

#define RANDOM 15      //PA2 - Unused pin

#define COLOR_BALL srn::C_RED
#define COLOR_BLOCK srn::C_GRN
#define COLOR_BAT srn::C_ORG
#define COLOR_ERASE srn::C_BLACK


// ball
uint8_t ballX;              //Current ball column
uint8_t ballY;              //Current ball row
bool ballRight;             //True if ball moves right
bool ballDown;              //True if ball moves down
bool ballInPlay;            //True when ball is moving

// Bat
uint8_t batX;               //Current bat position
uint8_t newBatX;            //Next bat position
int autoRandom;             //Random movement to appl to bat in autoplay

//Blocks
uint8_t blockCount;         //Count of blocks currently on screen
bool enableBlockBounce;     //True to have ball bounce off blocks

// Game play
int score;                  //Current score
int lives;                  //Number of lives left
bool switchText;            //Used to switch between game name and score
unsigned int gameSpeed;     //Used to speed  up game
unsigned long loopTimeout;  //Used to time out ball movement
bool gameOver;              //Status of game
bool autoPlay;              //Whether autoplay mode enabled

#define BAT_SPEED 50        //Speed in which bat can move across screen
unsigned long batTimeout;   //Used to time out bat movement

#define BONUS_SCORE 50      //Bonus score for clearing board
#define NUM_OF_LIVES 5      //Number of lives player gets
#define INITIAL_SPEED 200   //Initial speed of ball
#define MAXIMUM_SPEED 50    //Maximum speed of ball
#define STEP_SPEED 10       //Speed increase after clearing screen

//-------------------------------------------------------------------------
//Initialise Hardware

void setup()
{
  //Setup screen
  srn::setup();
  srn::clearDisplay();     //Clear the primary buffer
  srn::refresh();          //Transfer to display buffer

  //Setup sound
  play::setup();

  gameOver = true;
  randomSeed(analogRead(RANDOM)); // better random numbers

  pinMode(SW_LF, INPUT_PULLUP);
  pinMode(SW_RG, INPUT_PULLUP);
  pinMode(SW_BA, INPUT_PULLUP);
  pinMode(SW_BB, INPUT_PULLUP);
  pinMode(SW_BC, INPUT_PULLUP);

  delay(500);
  srn::scrollDelay = 0;     //Force refresh of scrolling text
}

//-------------------------------------------------------------------------
// Handle interactions
void loop()
{
  if (!gameOver)
  {
    testButtons();
    if (gameOver)
    {
      play::loseSound();
      srn::clearDisplay();
      switchText = true;
      delay(500);
    }
    else if (millis() > loopTimeout)
    {
      if (!moveBall())
      {
        if (blockCount == 0)
        {
          srn::refresh();
          play::winSound();
          //round is over so give the player an extra life
          lives++;
          score += BONUS_SCORE; //Bonus for clearing board
          setupRound();
          if (gameSpeed > MAXIMUM_SPEED)
          {
            gameSpeed -= STEP_SPEED;  //Speed up ball
          }
        }
        else if (lives > 0)
        {
          //Continue round
          lives--;
        }
        else 
        {
          //Out of lives
          gameOver = true;
          play::loseSound();
          srn::clearDisplay();
          switchText = true;
          delay(500);
        }
      }
      srn::refresh();
      loopTimeout = millis() + gameSpeed;
    }
  }

  if (gameOver)
  {
    //wait for button A to be pressed
    if (buttonPressedAndReleased(SW_BA))
    {
      enableBlockBounce = true;
      autoPlay = false;
      setupGame();            
    }
    else if (buttonPressedAndReleased(SW_BB))
    {
      enableBlockBounce = false;
      autoPlay = false;
      setupGame();            
    }
    else if (buttonPressedAndReleased(SW_BC))
    {
      enableBlockBounce = true;
      autoPlay = true;
      setupGame();
    }
    else if (srn::scrollDelay == 0)
    {
      if (score > 0 && switchText)
      {
        srn::drawString("SCORE:" + String(score), COLOR_BLOCK);
      }
      else
      {
        srn::drawString("BREAKOUT", COLOR_BALL);
      }
      switchText = !switchText;
    }
  }
}

//-------------------------------------------------------------------------
// initialise a game

void setupGame()
{
  score = 0;
  lives = NUM_OF_LIVES;
  gameSpeed = INITIAL_SPEED;
  gameOver = false;
  
  setupRound();
  
}

//-------------------------------------------------------------------------
// initialise a round

void setupRound()
{
  //Clear the screen
  srn::scrollDelay = 0;     //Stop scrolling text
  srn::clearDisplay();
  
  //Draw the bat
  batX = 3;
  newBatX = batX;
  srn::setPixel(7, batX+0, COLOR_BAT);
  srn::setPixel(7, batX+1, COLOR_BAT);
  srn::setPixel(7, batX+2, COLOR_BAT);

  //Draw blocks
  blockCount = 0;
  uint8_t y1;
  for (int y = 0; y < 5; y = y + 2)
  {
    for (int x = 0; x < 8; x++)
    {
      y1 = y + ((x & 0x02) ? 0 : 1);
      if (y1 < 5)
      {
        blockCount++;
        srn::setPixel(y1, x, COLOR_BLOCK);
      }
    }
  }

  loopTimeout = millis();
  batTimeout = millis();
  
  ballInPlay = false;
  autoRandom = 1;
  srn::refresh();
}

//-------------------------------------------------------------------------
// handle buttons
void testButtons()
{
  if (!gameOver)
  {
    if (millis() > batTimeout)
    {
      if (digitalRead(SW_BC) == LOW)
      {
        //Terminate play
        lives = 0;
        gameOver = true;
      }
      else if (buttonPressed(SW_LF))
      {
        if (ballInPlay)
        {    
          newBatX = (batX == 0) ? batX : batX - 1;
          moveBat();
        }
        else
        {
          ballX = batX + 1;
          ballY = 6;
          ballRight = false;
          ballDown = false;
          ballInPlay = true;
          srn::setPixel(ballY, ballX, COLOR_BALL);
        }
        srn::refresh();
        //loopTimeout = millis() + gameSpeed;
        batTimeout = millis() + BAT_SPEED;
      }
      else if (buttonPressed(SW_RG))
      {
        if (ballInPlay)
        {    
          newBatX = (batX == 5) ? batX : batX + 1;
          moveBat();
        }
        else
        {
          ballX = batX + 1;
          ballY = 6;
          ballRight = true;
          ballDown = false;
          ballInPlay = true;
          srn::setPixel(ballY, ballX, COLOR_BALL);
        }
        srn::refresh();
        //loopTimeout = millis() + gameSpeed;
        batTimeout = millis() + BAT_SPEED;
      }
    }
  }
}

//-------------------------------------------------------------------------
// Get user input
bool buttonPressed(int pin)
{
  bool result = false;
  if (!autoPlay)
  {
    result = (digitalRead(pin) == LOW);
  }
  else if (!ballInPlay)
  {
    result = true;
  }
  else if (pin == SW_LF)
  {
    result = (ballX < (batX + autoRandom + ((ballRight) ? -1 : 1)) && batX > 0);
  }
  else if (pin == SW_RG)
  {
    result = (ballX > (batX + autoRandom + ((ballRight) ? -1 : 1)) && batX < 5);
  }
  return result;
}

//-------------------------------------------------------------------------
// Test if button pressed with debouncing and wait until release
//  p - button pin to test
bool buttonPressedAndReleased(uint8_t p)
{
  bool pressed = false;
  if (digitalRead(p) == LOW)
  {
    delay(10);                      //Debounce 10mS
    if (digitalRead(p) == LOW)
    {
      while (digitalRead(p) == LOW) ;   //Wait for release
      pressed = true;
    }
  }
  return pressed;
}

//-------------------------------------------------------------------------
// Move the bat
void moveBat()
{
  if (newBatX != batX)
  {
    srn::setPixel(7, batX+0, COLOR_ERASE);
    srn::setPixel(7, batX+1, COLOR_ERASE);
    srn::setPixel(7, batX+2, COLOR_ERASE);
    batX = newBatX;
    srn::setPixel(7, batX+0, COLOR_BAT);
    srn::setPixel(7, batX+1, COLOR_BAT);
    srn::setPixel(7, batX+2, COLOR_BAT);
  }
}

//-------------------------------------------------------------------------
// Move the ball
//  - Bounce off left, right and top walls
//  - Bounce off bat
//  Returns true if ball is still in play
bool moveBall()
{
  bool roundOver = false;
  bool hitBlock = false;
  int8_t nx, ny;
  
  if (ballInPlay)
  {
    //Test if we need hit the edge and change direction if necessary
    if (ballRight && ballX == 7)
    {
      ballRight = false;        //We are on right edge so bounce left
    }
    else if (!ballRight && ballX == 0)
    {
      ballRight = true;         //We are on left edge so bounce right
    }

    //Test for vertical limits and hitting bat
    nx = ballX + ((ballRight) ? 1 : -1);
    if (ballDown && ballY == 7)
    {
      roundOver = true;         //We are off the bottom of the screen
      ballInPlay = false;
      play::missTone();
    }
    else if (ballDown && ballY == 6)
    {
      //We are about to move to row 7. Check if we are about to hit the bat
      if (batX+0 == nx)
      {
        srn::setPixel(ballY, ballX, COLOR_ERASE);
        ballX++;                //Move ball one pixel to the right
        ballDown = false;       //We hit the bat so bounce up
        play::batTone(523);
      }
      else if (batX+2 == nx)
      {
        srn::setPixel(ballY, ballX, COLOR_ERASE);
        ballX--;                //Move ball one pixel to the left
        ballDown = false;       //We hit the bat so bounce up
        play::batTone(523);
      }
      else if (batX+1 == nx)
      {
        ballDown = false;       //We hit the bat so bounce up
        play::batTone(440);
      }
    }
    else if (!ballDown && ballY == 0)
    {
      ballDown = true;          //We are on top edge so bounce down
    }

    //Test if we are about to hit a blpock
    nx = ballX + ((ballRight) ? 1 : -1);
    ny = ballY + ((ballDown) ? 1 : -1);
    if (!roundOver && srn::getPixel(ny, nx) == COLOR_BLOCK)
    {
      //About to hit block
      hitBlock = true;
      srn::setPixel(ny, nx, COLOR_ERASE);
      score++;                  //Count each block hit
      blockCount--;             //Count down blocks
      if (blockCount > 0)
      {
        play::hitTone();
      }
    }

    //Move the ball
    srn::setPixel(ballY, ballX, COLOR_ERASE);
    if (!roundOver)
    {
      ballX = (ballX + ((ballRight) ? 1 : -1)) & 0x07;
      ballY = (ballY + ((ballDown) ? 1 : -1)) & 0x07;
      srn::setPixel(ballY, ballX, COLOR_BALL);
    }

    //Change Y direction if we hit a block
    if (enableBlockBounce && hitBlock)
    {
      ballDown = !ballDown; //Change vertical direction
    }
    
    //redraw bat
    srn::setPixel(7, batX+0, COLOR_BAT);
    srn::setPixel(7, batX+1, COLOR_BAT);
    srn::setPixel(7, batX+2, COLOR_BAT);

    autoRandom = (int)random(3); //Allow random bat adjustments in autoPlay
  }

  
  //check for clearance
  if (blockCount == 0)
  {
    roundOver = true;
  }
  return !roundOver;
}

Display.h

C/C++
/*
Namespace: Display
Author: John Bradnam (jbrad2089@gmail.com)
Purpose: 8x8 Red/Green Matrix display routines
*/
#pragma once
#include <SPI.h>// SPI Library used to clock data out to the shift registers

namespace srn
{
  
#define LATCH_PIN 13   //PC3 can use any pin you want to latch the shift registers
#define BLANK_PIN 8    //PB1 same, can use any pin you want for this, just make sure you pull up via a 1k to 5V
#define DATA_PIN 14    //PA1 used by SPI, must be pin 11
#define CLOCK_PIN 16   //PA3 used by SPI, must be 13
#define ANODE_A 7      //PB2 74138 A Input
#define ANODE_B 6      //PB3 74138 B Input
#define ANODE_C 5      //PB4 74138 C Input

#define BLANK_PORT    PORTB
#define BLANK_BM      PIN1_bm
#define LATCH_PORT    PORTC
#define LATCH_BM      PIN3_bm

#define ROWS 8            //Number of rows of LEDs
#define LEDS_PER_ROW 16   //Number of leds on each row
#define BYTES_PER_ROW 2   //Number of bytes required to hold one bit per LED in each row

enum CMODE { C_BLACK, C_RED, C_GRN, C_ORG };

//Bit buffer for matrix
byte ledStates[ROWS][BYTES_PER_ROW];  //Store state of each LED (either off or on)
byte ledNext[ROWS][BYTES_PER_ROW];    //Double buffer for fast updates
int8_t activeRow = 0;                 //this increments through the anode levels

#define SCROLL_SPEED 30               //Speed at which text is scrolled
String scrollText;                    //Used to store scrolling text
volatile int8_t scrollDelay;          //Used to store scroll delay
volatile uint8_t scrollCharPos;       //Current character position in text being scrolled
volatile int8_t scrollCharCol;        //Next column in character to display
volatile int8_t scrollOffScreen;      //Extra columns required to scroll last character off screen
volatile CMODE scrollColor;           //String color

//Font
#define SPACE 16
const uint8_t font5x7 [43][5] PROGMEM = {
  {0x3E, 0x51, 0x49, 0x45, 0x3E}, // 0 
  {0x00, 0x42, 0x7F, 0x40, 0x00}, // 1 
  {0x42, 0x61, 0x51, 0x49, 0x46}, // 2 
  {0x21, 0x41, 0x45, 0x4B, 0x31}, // 3 
  {0x18, 0x14, 0x12, 0x7F, 0x10}, // 4 
  {0x27, 0x45, 0x45, 0x45, 0x39}, // 5 
  {0x3C, 0x4A, 0x49, 0x49, 0x30}, // 6 
  {0x01, 0x71, 0x09, 0x05, 0x03}, // 7 
  {0x36, 0x49, 0x49, 0x49, 0x36}, // 8 
  {0x06, 0x49, 0x49, 0x29, 0x1E}, // 9 
  {0x00, 0x36, 0x36, 0x00, 0x00}, // : 
  {0x00, 0x56, 0x36, 0x00, 0x00}, // ; 
  {0x08, 0x14, 0x22, 0x41, 0x00}, // < 
  {0x14, 0x14, 0x14, 0x14, 0x14}, // = 
  {0x00, 0x41, 0x22, 0x14, 0x08}, // > 
  {0x02, 0x01, 0x51, 0x09, 0x06}, // ? 
  {0x00, 0x00, 0x00, 0x00, 0x00}, // Space 
  {0x7C, 0x12, 0x11, 0x12, 0x7C}, // A 
  {0x7F, 0x49, 0x49, 0x49, 0x36}, // B 
  {0x3E, 0x41, 0x41, 0x41, 0x22}, // C 
  {0x7F, 0x41, 0x41, 0x22, 0x1C}, // D 
  {0x7F, 0x49, 0x49, 0x49, 0x41}, // E 
  {0x7F, 0x09, 0x09, 0x09, 0x01}, // F 
  {0x3E, 0x41, 0x49, 0x49, 0x7A}, // G 
  {0x7F, 0x08, 0x08, 0x08, 0x7F}, // H 
  {0x00, 0x41, 0x7F, 0x41, 0x00}, // I 
  {0x20, 0x40, 0x41, 0x3F, 0x01}, // J 
  {0x7F, 0x08, 0x14, 0x22, 0x41}, // K 
  {0x7F, 0x40, 0x40, 0x40, 0x40}, // L 
  {0x7F, 0x02, 0x0C, 0x02, 0x7F}, // M 
  {0x7F, 0x04, 0x08, 0x10, 0x7F}, // N 
  {0x3E, 0x41, 0x41, 0x41, 0x3E}, // O 
  {0x7F, 0x09, 0x09, 0x09, 0x06}, // P 
  {0x3E, 0x41, 0x51, 0x21, 0x5E}, // Q 
  {0x7F, 0x09, 0x19, 0x29, 0x46}, // R 
  {0x46, 0x49, 0x49, 0x49, 0x31}, // S 
  {0x01, 0x01, 0x7F, 0x01, 0x01}, // T 
  {0x3F, 0x40, 0x40, 0x40, 0x3F}, // U 
  {0x1F, 0x20, 0x40, 0x20, 0x1F}, // V 
  {0x3F, 0x40, 0x38, 0x40, 0x3F}, // W 
  {0x63, 0x14, 0x08, 0x14, 0x63}, // X 
  {0x07, 0x08, 0x70, 0x08, 0x07}, // Y 
  {0x61, 0x51, 0x49, 0x45, 0x43}  // Z 
};

//-------------------------------------------------------------------------
//Forward references
void setup();
void refresh();
void drawString(String s, CMODE color);
void scrollTextLeft();
void drawCharacter(int8_t x, char ch, CMODE color);
void setPixel(int8_t r, int8_t c, CMODE color);
CMODE getPixel(int8_t r, int8_t c);
int8_t mapColumn(int8_t c, bool g);
int8_t mapColumnRed(int8_t c);
int8_t mapColumnGrn(int8_t c);
void setBitInArray(int8_t r, int8_t c, bool on);
bool getBitInArray(int8_t r, int8_t c);
void clearDisplay();

//-------------------------------------------------------------------------
//Initialise Hardware

void setup()
{
  SPI.setBitOrder(MSBFIRST);//Most Significant Bit First
  SPI.setDataMode(SPI_MODE0);// Mode 0 Rising edge of data, keep clock low
  SPI.setClockDivider(SPI_CLOCK_DIV2);//Run the data in at 16MHz/2 - 8MHz

  noInterrupts();// kill interrupts until everybody is set up
  activeRow = 0;

  //Set up display refresh timer
  //CLK_PER = 3.3MHz (303nS)
  TCB1.CCMP = 49152;   //Refresh value for display (67Hz)
  TCB1.INTCTRL = TCB_CAPT_bm;
  TCB1.CTRLA = TCB_ENABLE_bm;

  //finally set up the Outputs
  pinMode(LATCH_PIN, OUTPUT);//Latch
  pinMode(DATA_PIN, OUTPUT);//MOSI DATA
  pinMode(CLOCK_PIN, OUTPUT);//SPI Clock
  //Setup pins to 3 to 8 channel multiplexer
  pinMode(ANODE_A, OUTPUT);
  pinMode(ANODE_B, OUTPUT);
  pinMode(ANODE_C, OUTPUT);

  //pinMode(BLANK_PIN, OUTPUT);//Output Enable  important to do this last, so LEDs do not flash on boot up
  SPI.begin();//start up the SPI library
  interrupts();//let the show begin, this lets the multiplexing start
}

//-------------------------------------------------------------------------
//Timer B Interrupt handler interrupt each mS - output segments
ISR(TCB1_INT_vect)
{

  BLANK_PORT.OUTSET = BLANK_BM; //The first thing we do is turn all of the LEDs OFF, by writing a 1 to the blank pin
  
  //Turn on all columns
  for (int shift_out = 0; shift_out < BYTES_PER_ROW; shift_out++)
  {
    SPI.transfer(ledStates[activeRow][shift_out]);
  }

  //Enable row that we just outputed the column data for
  digitalWrite(ANODE_A, (activeRow & 0x01) ? LOW : HIGH);
  digitalWrite(ANODE_B, (activeRow & 0x02) ? LOW : HIGH);
  digitalWrite(ANODE_C, (activeRow & 0x04) ? LOW : HIGH);

  LATCH_PORT.OUTSET = LATCH_BM; //Latch pin HIGH
  LATCH_PORT.OUTCLR = LATCH_BM; //Latch pin LOW
  BLANK_PORT.OUTCLR = BLANK_BM; //Blank pin LOW to turn on the LEDs with the new data

  activeRow = (activeRow + 1) % ROWS;   //increment the active row
  
  BLANK_PORT.DIRSET = BLANK_BM; //Make BLANK pin an OUTPUT

  //Handle scrolling of text
  if (scrollDelay != 0)
  {
    scrollDelay--;
    if (scrollDelay == 0)
    {
      scrollDelay = SCROLL_SPEED;
      scrollTextLeft();
    }
  }

  //Clear interrupt flag
  TCB1.INTFLAGS |= TCB_CAPT_bm; //clear the interrupt flag(to reset TCB1.CNT)
}

//---------------------------------------------------------------
//Transfers the working buffer to the display buffer
void refresh()
{
  memcpy(ledStates, ledNext, ROWS * BYTES_PER_ROW);
}

//---------------------------------------------------------------
//Draw string
// s = String to display
// col = color to show
void drawString(String s, CMODE color)
{
  if (scrollDelay == 0)
  {
    scrollText = s;
    scrollColor = color;
    scrollCharPos = 0;
    scrollCharCol = 0;
    scrollOffScreen = 0;
    scrollDelay = SCROLL_SPEED;     //Starts scrolling
  }
}

//---------------------------------------------------------------
//Scroll text left
void scrollTextLeft()
{
  uint8_t bits;
  uint8_t mask;

  //Scroll screen buffer left
  for (int8_t c = 0; c < 8; c++)
  {
    for (int8_t r = 0; r < 8; r++)
    {
      setPixel(r, c, (c == 7) ? C_BLACK : getPixel(r, c + 1));
    }
  }

  //Sixth character column is blank for letter spacing
  if (scrollOffScreen == 0 && scrollCharCol < 5)
  {
    char ch = scrollText[scrollCharPos];
    if (ch >= 48 && ch <= 90)
    {
      //Get bits in the next column and output to buffer
      bits = pgm_read_byte(&font5x7[ch-48][scrollCharCol]);
      mask = 0x40;
      for(int8_t r = 0; r < 7; r++)
      {
        if (bits & mask)
        {
          setPixel(7 - r, 7, scrollColor);
        }
        mask = mask >> 1;
      }
    }
  }

  if (scrollOffScreen > 0)
  {
    scrollOffScreen--;
    if (scrollOffScreen == 0)
    {
      //Stop scrolling
      scrollDelay = 0;
    }
  }
  else
  {
    scrollCharCol++;
    if (scrollCharCol == 6)
    {
      scrollCharCol = 0;
      scrollCharPos++;
      if (scrollCharPos == scrollText.length())
      {
        //All text has been outputted, just wait until it is scrolled of the screen
        scrollOffScreen = 8;
      }
    }
  }
  refresh();
}

//---------------------------------------------------------------
//Draw character
// x = column (0-7) 0 being farest left hand and 7 being farest right
// ch = ASCII character
// col = color to show
void drawCharacter(int8_t x, char ch, CMODE color)
{
  uint8_t bits;
  uint8_t mask;
  if (ch >= 48 && ch <= 90)
  {
    ch = ch - 48;
    for(int c = 0; c < 6; c++)
    {
      int8_t pos = c + x;
      if (pos >= 0 && pos < 8)
      {
        if (c == 5)
        {
          //Blank column for character spacing
          for(int r = 0; r < 7; r++)
          {
            setPixel(r, pos, C_BLACK);
          }
        }
        else
        {
          bits = pgm_read_byte(&font5x7[(int)ch][c]);
          mask = 0x40;
          for(int r = 0; r < 7; r++)
          {
            if (bits & mask)
            {
              setPixel(7 - r, pos, color);
            }
            mask = mask >> 1;
          }
        }
      }
    }
  }
}

//---------------------------------------------------------------
//Sets the bit in the 6 byte array that corresponds to the physical column and row
// r = row (0 - top row to 7 - bottom row)
// c = column (0 to 7)
// color = color to set pixel
// on = true to switch bit on, false to switch bit off
void setPixel(int8_t r, int8_t c, CMODE color)
{
  if (r >= 0 && r < 8 && c >= 0 && c < 8)
  {
    switch (color)
    {
      case C_BLACK:
        setBitInArray(r, mapColumnRed(c), false);
        setBitInArray(r, mapColumnGrn(c), false);
        break;
            
      case C_RED:
        setBitInArray(r, mapColumnRed(c), true);
        setBitInArray(r, mapColumnGrn(c), false);
        break;
      
      case C_GRN:
        setBitInArray(r, mapColumnRed(c), false);
        setBitInArray(r, mapColumnGrn(c), true);
        break;
      
      case C_ORG:
        setBitInArray(r, mapColumnRed(c), true);
        setBitInArray(r, mapColumnGrn(c), true);
        break;
    }
  }
}

//---------------------------------------------------------------
//Get the color of the bit that corresponds to the physical column and row
// r = row (0 - top row to 7 - bottom row)
// c = column (0 to 7)
// Returns color of pixel (BLACK, RED, GREEN, ORANGE)
CMODE getPixel(int8_t r, int8_t c)
{
  bool red = getBitInArray(r, mapColumnRed(c));
  bool grn = getBitInArray(r, mapColumnGrn(c));
  if (red && grn)
  {
    return C_ORG;
  }
  else if (red)
  {
    return C_RED;
  }
  else if (grn)
  {
    return C_GRN;
  }
  return C_BLACK;
}

//---------------------------------------------------------------
//Maps matrix, column and color to LED column number
// c = column (0-7) 0 being farest left hand and 7 being farest right
// g = green - true for green, false for red
// returns physical column (0 to 47)
int8_t mapColumn(int8_t c, bool g)
{
  return ((c < 4) ? (8 + (c << 1) + ((g) ? 0 : 1)) : (8 - ((c - 3) << 1) + ((g) ? 1 : 0)));
}

//---------------------------------------------------------------
//Maps matrix and red column to LED column number
// c = column (0-7) 0 being farest left hand and 7 being farest right
// returns physical column (0 to 47)
int8_t mapColumnRed(int8_t c)
{
  return ((c < 4) ? (9 + (c << 1)) : (8 - ((c - 3) << 1)));
}

//---------------------------------------------------------------
//Maps matrix and green column and color to LED column number
// c = column (0-7) 0 being farest left hand and 7 being farest right
// returns physical column (0 to 47)
int8_t mapColumnGrn(int8_t c)
{
  return ((c < 4) ? (8 + (c << 1)) : (9 - ((c - 3) << 1)));
}

//---------------------------------------------------------------
//Sets the bit in the 6 byte array that corresponds to the physical column and row
// r = row (0 - top row to 7 - bottom row)
// c = column (0 to 15)
// on = true to switch bit on, false to switch bit off
void setBitInArray(int8_t r, int8_t c, bool on)
{
  uint8_t by = c >> 3;
  uint8_t bi = c - (by << 3);
  //Serial.println("r:" + String(7 - r) + ", c:" + String(c) + ", by:" + String(by) + ", bi:" + String(bi));
  if (on)
  {
    ledNext[7 - r][by] |= (1 << bi);
  }
  else
  {
    ledNext[7 - r][by] &= ~(1 << bi);
  }
}

//---------------------------------------------------------------
//Gets the bit that corresponds to the physical column and row
// r = row (0 - top row to 7 - bottom row)
// c = column (0 to 15)
// returns true if bit on
bool getBitInArray(int8_t r, int8_t c)
{
  uint8_t by = c >> 3;
  uint8_t bi = c - (by << 3);
  return (ledNext[7 - r][by] & (1 << bi));
}

//---------------------------------------------------------------
//Clears the working buffer
void clearDisplay()
{
  //Clear out ledStates array
  for (int r = 0; r < ROWS; r++)
  {
    for (int c = 0; c < BYTES_PER_ROW; c++)
    {
      ledNext[r][c] = 0;
    }
  }
}

} //namespace

Sound.h

C/C++
/*
Namespace: Sound
Author: John Bradnam (jbrad2089@gmail.com)
Purpose: Sound routines
*/
#pragma once
#include <TimerFreeTone.h>  // https://bitbucket.org/teckel12/arduino-timer-free-tone/wiki/Home

namespace play
{

#define SPEAKER 12     //PC2
  
//-------------------------------------------------------------------------
//Forward references
void setup();
void hitTone();
void batTone(int note);
void missTone();
void noteDecay(int start); 
void noteAttack(int start); 
void winSound();
void loseSound();

//-------------------------------------------------------------------------
//Initialise Hardware
void setup()
{
  pinMode(SPEAKER,OUTPUT);
}

//------------------------------------------------------------------
//Play valid move sound
void hitTone()
{
  TimerFreeTone(SPEAKER, 262, 50); 
}

//------------------------------------------------------------------
//Play valid move sound
void batTone(int note)
{
  TimerFreeTone(SPEAKER, note, 50); 
}

//------------------------------------------------------------------
//Play bad move sound
void missTone()
{
  TimerFreeTone(SPEAKER, 65, 150); 
}

//-----------------------------------------------------------------------------------
//Play a decaying sound
void noteDecay(int start) 
{
  #define DECAY_NOTE 100                // Minimum delta time.
  
  for (int note = start; note >= DECAY_NOTE; note -= 10)
  {                       
    TimerFreeTone(SPEAKER, note, 100);
  }
}

//-----------------------------------------------------------------------------------
//Play a attack sound
void noteAttack(int start) 
{
  #define DECAY_NOTE 100                // Minimum delta time.
  
  for (int note = DECAY_NOTE; note <= start; note += 10)
  {                       
    TimerFreeTone(SPEAKER, note, 100);
  }
}

//------------------------------------------------------------------
//Play a high note as a sign you lost
void winSound()
{
  //TimerFreeTone(SPEAKER,880,300);
  TimerFreeTone(SPEAKER,880,100); //A5
  TimerFreeTone(SPEAKER,988,100); //B5
  TimerFreeTone(SPEAKER,523,100); //C5
  TimerFreeTone(SPEAKER,988,100); //B5
  TimerFreeTone(SPEAKER,523,100); //C5
  TimerFreeTone(SPEAKER,587,100); //D5
  TimerFreeTone(SPEAKER,523,100); //C5
  TimerFreeTone(SPEAKER,587,100); //D5
  TimerFreeTone(SPEAKER,659,100); //E5
  TimerFreeTone(SPEAKER,587,100); //D5
  TimerFreeTone(SPEAKER,659,100); //E5
  TimerFreeTone(SPEAKER,659,100); //E5
  delay(250);
}

//------------------------------------------------------------------------------------------------------------------
//Play wah wah wah wahwahwahwahwahwah
void loseSound()
{
  delay(400);
  //wah wah wah wahwahwahwahwahwah
  for(double wah=0; wah<4; wah+=6.541)
  {
    TimerFreeTone(SPEAKER, 440+wah, 50);
  }
  TimerFreeTone(SPEAKER, 466.164, 100);
  delay(80);
  for(double wah=0; wah<5; wah+=4.939)
  {
    TimerFreeTone(SPEAKER, 415.305+wah, 50);
  }
  TimerFreeTone(SPEAKER, 440.000, 100);
  delay(80);
  for(double wah=0; wah<5; wah+=4.662)
  {
    TimerFreeTone(SPEAKER, 391.995+wah, 50);
  }
  TimerFreeTone(SPEAKER, 415.305, 100);
  delay(80);
  for(int j=0; j<7; j++)
  {
    TimerFreeTone(SPEAKER, 391.995, 70);
    TimerFreeTone(SPEAKER, 415.305, 70);
  }
  delay(400);
}

} //namespace

Credits

John Bradnam
147 projects • 181 followers

Comments