Hardware components | ||||||
| × | 1 | ||||
Software apps and online services | ||||||
|
This breakout game was written for my Matrix Game Console. The display for this console is built using a 8x8 Red/Green LED matrix. This does present some challenges when designing games to run on it because 64 LEDs in a 8 by 8 grid isn't a lot of resolution.
The game console has a X pad and three extra buttons labelled A, B and C.
The game has three modes selectable by buttons A, B and C.
Button A - Traditional Breakout. In this mode, the ball will bounce off any blocks that it comes in contact with. That is it will change direction so you need to be careful when the ball leaves the bat as it can hit one of the lower blocks and come back quickly.
You are given 5 lives and you get an extra life every time you clear all the blocks. Your score is increased each time you hit a block and you get an extra 50 bonus points each time you complete a round. Also the ball increases in speed upon completion of each round.
Launch a ball by pressing either the left or right side of the X-Pad. Left will send the ball left, right will send the ball to the right. The left and right sides of the X-Pad controls the bat.
Pressing Button C at anytime will terminate the game. Also running out of lives will also terminate the game. The final score will be displayed.
Button B - Simplified Breakout. In this mode, the ball won't bounce off the blocks that it comes in contact with. That is it will continue in the same direction only bouncing off the sides. This makes it easier to determine where to place the bat to keep the ball in play.
In all other aspects, the game is the same as the Traditional Breakout game described above.
Button C - Autoplay. In this mode, the computer controls the bat and you can sit back and watch it complete round after round.
Pressing Button C at anytime will terminate the game. Also running out of lives will also terminate the game but this is unlikely as the computer rarely misses. The final score will be displayed.
StrategyIf the ball bounces off the middle of the bat, the ball will leave the bat at the point of contact. If the ball bounces of the left side of the bat, the ball will leave the bat one pixel to the left of the point of contact. Similarly if the ball bounces of the right side of the bat, the ball will leave the bat one pixel to the right of the point of contact.
The reason for this is that if the ball always leaves the bat at the point of contact, you can end up in a situation where the ball follows the same trajectory over and over again with no way of stopping it. It also allows a skilled player to have some control over where the ball travels.
/**************************************************************************
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;
}
/*
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
/*
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
Comments