Hardware components | ||||||
| × | 1 | ||||
Software apps and online services | ||||||
![]() |
|
This Snake 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 two modes selectable by buttons A and C.
Button A - Manual Game. In this mode, the X pad will control the direction of the green snake. The object is to eat the red apple with the head of the snake. This will cause the snake to grow. After you eat the apple, another apple will appear in a random location. The apple also moves to a random location after a period of time. The speed of the snake increases each time you devour an apple.
If the snake moves outside the boundaries of the game area or runs into itself, the game is over and you lose. If you eat more than 40 apples, the game also ends and you win.
Button C - Autoplay. In this mode, the computer controls the snake and you can sit back and watch it grow until it runs into itself.
/**************************************************************************
Snake V1
Author: John Bradnam (jbrad2089@gmail.com)
2022-03-24
Create program for ATtiny3216
Added Autoplay mode
--------------------------------------------------------------------------
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)
**************************************************************************/
#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
enum DIRECTION { LEFT, RIGHT, UP, DOWN };
//board
#define APPLE_COLOR srn::C_RED
#define SNAKE_COLOR srn::C_GRN
#define MAX_SIZE 64 //Max queue length (must be a power of 2)
#define MAX_MASK (MAX_SIZE-1)
uint8_t head; //Circular queue head pointer
uint8_t tail; //Circular queue tail pointer
uint8_t board[MAX_SIZE]; //Circular queue
#define SNAKE_START 3*8+2 //Start at position 3,2 and 3,3
uint8_t snakeLocation; //Current head of snake position
//Apple
#define MOVES_PER_APPLE 40 //Snake movements before apple moves
uint8_t appleLocation; //Current apple location
uint8_t appleCountDown; //Used to count down moves before apple moves
//Game play
bool computerPlay; //True for computer to play game
int score; //Current score
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 snake movement
bool gameOver; //Status of game
DIRECTION snakeDir; //Current direction
#define INITIAL_SPEED 200 //Initial speed of snake
#define MAXIMUM_SPEED 50 //Maximum speed of snake
#define STEP_SPEED 5 //Speed snake increases after hit on apple
#define WINNING_SCORE 40 //Maximum score
//-------------------------------------------------------------------------
//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_UP, INPUT_PULLUP);
pinMode(SW_DN, INPUT_PULLUP);
pinMode(SW_BA, INPUT_PULLUP);
pinMode(SW_BC, INPUT_PULLUP);
srn::scrollDelay = 0; //Force refresh of scrolling text
}
//-------------------------------------------------------------------------
// initialise a game
void setupGame(bool autoPlay)
{
//Clear the screen
srn::scrollDelay = 0; //Stop scrolling text
srn::clearDisplay();
//Clear board
for(uint8_t i=0; i<MAX_SIZE; i++)
{
board[i] = 0;
}
//Initialise 2 pixel snake
snakeLocation = SNAKE_START;//Start at position 3,3
board[0] = snakeLocation; //Place on board
showPixel(snakeLocation, SNAKE_COLOR);
snakeLocation++;
board[1] = snakeLocation; //Place on board
showPixel(snakeLocation, SNAKE_COLOR);
head = 2; //Current Queue head
tail = 0; //Current Queue tail
snakeDir = RIGHT; //Going right
//Initialise apple
moveApple();
//Setup timers
gameSpeed = INITIAL_SPEED;
loopTimeout = millis();
score = 2; //Score is length of snake
gameOver = false;
computerPlay = autoPlay;
srn::refresh();
}
//-------------------------------------------------------------------------
// Handle interactions
void loop()
{
if (!gameOver)
{
if (!computerPlay)
{
userButtons();
}
if (millis() > loopTimeout)
{
if (computerPlay)
{
computerButtons();
}
//Move snake
snakeLocation = moveSnake(snakeLocation, snakeDir);
if (hitSnake(snakeLocation) || hitWall(snakeLocation, snakeDir))
{
//Hit ourselves so game is over
play::loseSound();
srn::clearDisplay();
switchText = true;
gameOver = true;
delay(500);
}
else if (snakeLocation == appleLocation)
{
//Hit the apple so increase the length of the snake by 1
//This is achieved by not deleting the tail
showPixel(snakeLocation, SNAKE_COLOR);
board[head] = snakeLocation;
head = (head + 1) & MAX_MASK;
play::hitTone();
moveApple(); //Move the apple
score++; //Increase the score
if (score > WINNING_SCORE)
{
//Exceed maximum score so game is over
play::winSound();
srn::clearDisplay();
switchText = true;
gameOver = true;
delay(500);
}
else if (gameSpeed > MAXIMUM_SPEED)
{
gameSpeed -= STEP_SPEED; //Speed up snake
}
}
else
{
//Move snake
showPixel(snakeLocation, SNAKE_COLOR);
showPixel(board[tail], srn::C_BLACK);
board[head] = snakeLocation;
head = (head + 1) & MAX_MASK;
tail = (tail + 1) & MAX_MASK;
//Test if is time to move apple
appleCountDown--;
if (appleCountDown == 0)
{
showPixel(appleLocation, srn::C_BLACK);
moveApple(); //Move the apple
}
}
srn::refresh();
loopTimeout = millis() + gameSpeed;
}
}
if (gameOver)
{
if (digitalRead(SW_BA) == LOW) //User plays
{
setupGame(false);
}
else if (digitalRead(SW_BC) == LOW) //Auto play
{
setupGame(true);
}
else if (srn::scrollDelay == 0)
{
if (score > 0 && switchText)
{
srn::drawString("SCORE:" + String(score), SNAKE_COLOR);
}
else
{
srn::drawString("SNAKE", APPLE_COLOR);
}
switchText = !switchText;
}
}
}
//-------------------------------------------------------------------------
// Scan circular queue to see if pos is already used
// pos - square to test
// returns true if square is already part of snake
bool hitSnake(uint8_t pos)
{
uint8_t t = tail;
while (t != head && board[t] != pos)
{
t = (t + 1) & MAX_MASK;
}
return (t != head);
}
//-------------------------------------------------------------------------
// Test whether snake has hit the wall
// pos - square to test
// dir - current direction of snake
// x - direction on X axis
// returns true if hits the wall
bool hitWall(uint8_t pos, DIRECTION dir)
{
return (((pos & 0x07) == 0 && dir == RIGHT) ||
((pos & 0x07) == 7 && dir == LEFT) ||
((pos & 0xF8) == 0 && dir == DOWN) ||
((pos & 0xF8) == 56 && dir == UP));
}
//-------------------------------------------------------------------------
// Move the apple to a free square
void moveApple()
{
int timeout = 10;
do
{
appleLocation = (uint8_t)random(MAX_SIZE);
timeout--;
}
while (timeout != 0 && hitSnake(appleLocation));
if (hitSnake(appleLocation))
{
//Tried a number of random locations but all where taken
//so just get first free one found
for (appleLocation = 0; appleLocation < MAX_SIZE; appleLocation++)
{
if (!hitSnake(appleLocation))
{
break;
}
}
}
appleCountDown = MOVES_PER_APPLE;
showPixel(appleLocation, APPLE_COLOR);
}
//-------------------------------------------------------------------------
// Move the snake in the given direction
uint8_t moveSnake(uint8_t loc, DIRECTION dir)
{
switch (dir)
{
case LEFT: loc = (loc & 0xF8) + ((loc - 1) & 0x07); break;
case RIGHT: loc = (loc & 0xF8) + ((loc + 1) & 0x07); break;
case UP: loc = (loc - 8) & MAX_MASK; break;
case DOWN: loc = (loc + 8) & MAX_MASK; break;
}
return loc;
}
//-------------------------------------------------------------------------
// Draw the pixel at board location in specified color
// loc - Board location (row * 3 + column)
// color - CMODE constant
void showPixel(uint8_t loc, srn::CMODE color)
{
srn::setPixel(loc >> 3, loc & 0x07, color);
}
//-------------------------------------------------------------------------
// Test if X pad pressed
void userButtons()
{
if (testButtonStatus(SW_LF))
{
snakeDir = LEFT;
}
else if (testButtonStatus(SW_RG))
{
snakeDir = RIGHT;
}
else if (testButtonStatus(SW_UP))
{
snakeDir = UP;
}
else if (testButtonStatus(SW_DN))
{
snakeDir = DOWN;
}
}
//-------------------------------------------------------------------------
// Test if button pressed with debouncing
// p - button pin to test
bool testButtonStatus(uint8_t p)
{
if (digitalRead(p) == LOW)
{
delay(10); //Debounce 10mS
return (digitalRead(p) == LOW); //It will still be LOW if it wasn't a bounce
}
return false;
}
//-------------------------------------------------------------------------
// //Calculate best move
void computerButtons()
{
switch (snakeDir)
{
case LEFT: evaluateMovingLeft(); break;
case RIGHT: evaluateMovingRight(); break;
case UP: evaluateMovingUp(); break;
case DOWN: evaluateMovingDown(); break;
}
}
//-------------------------------------------------------------------------
// Currently moving left, evaluate what to do next
void evaluateMovingLeft()
{
int8_t sx = snakeLocation & 0x07;
int8_t sy = snakeLocation >> 3;
int8_t ax = appleLocation & 0x07;
int8_t ay = appleLocation >> 3;
if (sx == 0 || hitSnake(sy << 3 | (sx - 1)))
{
//will hit wall or ourselves
snakeDir = (sy > ay) ? UP : DOWN;
if (snakeDir == UP && (sy == 0 || hitSnake((sy - 1) << 3 | sx)))
{
//Can't go up either so must go down
snakeDir = DOWN;
}
else if (snakeDir == DOWN && (sy == 7 || hitSnake((sy + 1) << 3 | sx)))
{
//Can't go down either so must go up
snakeDir = UP;
}
}
else if (sx == ax)
{
//Go closer to ay
snakeDir = (sy > ay) ? UP : DOWN;
if (snakeDir == UP && (sy == 0 || hitSnake((sy - 1) << 3 | sx)))
{
//Can't go up so continue left
snakeDir = LEFT;
}
else if (snakeDir == DOWN && (sy == 7 || hitSnake((sy + 1) << 3 | sx)))
{
//Can't go down so continue LEFT
snakeDir = LEFT;
}
}
}
//-------------------------------------------------------------------------
// Currently moving right, evaluate what to do next
void evaluateMovingRight()
{
int8_t sx = snakeLocation & 0x07;
int8_t sy = snakeLocation >> 3;
int8_t ax = appleLocation & 0x07;
int8_t ay = appleLocation >> 3;
if (sx == 7 || hitSnake(sy << 3 | (sx + 1)))
{
//will hit wall or ourselves
snakeDir = (sy > ay) ? UP : DOWN;
if (snakeDir == UP && (sy == 0 || hitSnake((sy - 1) << 3 | sx)))
{
//Can't go up either so must go down
snakeDir = DOWN;
}
else if (snakeDir == DOWN && (sy == 7 || hitSnake((sy + 1) << 3 | sx)))
{
//Can't go down either so must go up
snakeDir = UP;
}
}
else if (sx == ax)
{
//Go closer to ay
snakeDir = (sy > ay) ? UP : DOWN;
if (snakeDir == UP && (sy == 0 || hitSnake((sy - 1) << 3 | sx)))
{
//Can't go up so continue RIGHT
snakeDir = RIGHT;
}
else if (snakeDir == DOWN && (sy == 7 || hitSnake((sy + 1) << 3 | sx)))
{
//Can't go down so continue RIGHT
snakeDir = RIGHT;
}
}
}
//-------------------------------------------------------------------------
// Currently moving up, evaluate what to do next
void evaluateMovingUp()
{
int8_t sx = snakeLocation & 0x07;
int8_t sy = snakeLocation >> 3;
int8_t ax = appleLocation & 0x07;
int8_t ay = appleLocation >> 3;
if (sy == 0 || hitSnake((sy - 1) << 3 | sx))
{
//will hit wall or ourselves
snakeDir = (sx > ax) ? LEFT : RIGHT;
if (snakeDir == LEFT && (sx == 0 || hitSnake(sy << 3 | (sx - 1))))
{
//Can't go left either so must go right
snakeDir = RIGHT;
}
else if (snakeDir == RIGHT && (sx == 7 || hitSnake(sy << 3 | (sx + 1))))
{
//Can't go right either so must go left
snakeDir = LEFT;
}
}
else if (sy == ay)
{
//Go closer to ax
snakeDir = (sx > ax) ? LEFT : RIGHT;
if (snakeDir == LEFT && (sx == 0 || hitSnake(sy << 3 | (sx - 1))))
{
//Can't go up so continue up
snakeDir = UP;
}
else if (snakeDir == RIGHT && (sx == 7 || hitSnake(sy << 3 | (sx + 1))))
{
//Can't go down so continue UP
snakeDir = UP;
}
}
}
//-------------------------------------------------------------------------
// Currently moving down, evaluate what to do next
void evaluateMovingDown()
{
int8_t sx = snakeLocation & 0x07;
int8_t sy = snakeLocation >> 3;
int8_t ax = appleLocation & 0x07;
int8_t ay = appleLocation >> 3;
if (sy == 7 || hitSnake((sy + 1) << 3 | sx))
{
//will hit wall or ourselves
snakeDir = (sx > ax) ? LEFT : RIGHT;
if (snakeDir == LEFT && (sx == 0 || hitSnake(sy << 3 | (sx - 1))))
{
//Can't go left either so must go right
snakeDir = RIGHT;
}
else if (snakeDir == RIGHT && (sx == 7 || hitSnake(sy << 3 | (sx + 1))))
{
//Can't go right either so must go left
snakeDir = LEFT;
}
}
else if (sy == ay)
{
//Go closer to ax
snakeDir = (sx > ax) ? LEFT : RIGHT;
if (snakeDir == LEFT && (sx == 0 || hitSnake(sy << 3 | (sx - 1))))
{
//Can't go up so continue DOWN
snakeDir = DOWN;
}
else if (snakeDir == RIGHT && (sx == 7 || hitSnake(sy << 3 | (sx + 1))))
{
//Can't go down so continue DOWN
snakeDir = DOWN;
}
}
}
/*
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 40 //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)
{
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 missTone();
void winSound();
void loseSound();
//-------------------------------------------------------------------------
//Initialise Hardware
void setup()
{
pinMode(SPEAKER,OUTPUT);
}
//------------------------------------------------------------------
//Play valid move sound
void hitTone()
{
TimerFreeTone(SPEAKER, 300, 150);
}
//------------------------------------------------------------------
//Play bad move sound
void missTone()
{
TimerFreeTone(SPEAKER, 50, 150);
}
//------------------------------------------------------------------
//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
Please log in or sign up to comment.