John Bradnam
Published © GPL3+

Snake Game

Snake game for the Matrix Game Console. Features built-in auto-play.

IntermediateFull instructions provided12 hours226

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

SnakeV1.ino

C/C++
/**************************************************************************
 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;
    }
  }
}      

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 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

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 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

Credits

John Bradnam

John Bradnam

141 projects • 167 followers

Comments