/*  Dual Colour Pong Clock adapted from Andrew Holme's RGB pong clock by Adrian Smith for use on a dual colour (red and yellow used) 16x32 matrix that uses
**  the HUB75 interface. Any 1/8 scan ( 3 address lines) matrix should work as long as it is compatible with the Adafruit RGB matrix libraries. 
**  Requires a Mega1280 / 2560 due to the size of the code and RAM required. 1.5Kb of SRAM required for the frame buffer on top of global variables used SRAM.  
**  Some AVR assembly language used in the libraries so not compatible with ARM based Arduino models.
**  Confirmed working with a DS3231 RTC which is much more accurate than a DS1307 and less time drift. DS3231 compatible with some DS1307 code.
**  Please see change log below at the bottom of this description text for more information.
**  Credit goes to Phillip Burgess / Paint your Dragon / Adafruit https://learn.adafruit.com/users/pburgess for the libraries used and
**  to Andrew / Nick as per the original comments below.
**   
**   
**  RGB Pong Clock - Andrew Holmes @pongclock
**  Inspired by, and shamelessly derived from 
**      Nick's LED Projects
**  https://123led.wordpress.com/about/
**  
**  Videos of the clock in action:
**  https://vine.co/v/hwML6OJrBPw
**  https://vine.co/v/hgKWh1KzEU0
**  https://vine.co/v/hgKz5V0jrFn
**  I run this on a Mega 2560, your mileage on other chips may vary,
**  Can definitely free up some memory if the bitmaps are shrunk down to size.

*
* V1.23
* 
* This version uses common anode 7 segment display with PNP transistors on the digit pins with sevenseg library
* Replaced digitalwrite calls with direct port manipulation commands
* Call gettime function at 12:00 so it syncronises with the RTC other than when the clock is booted up. For future versions could sync with NTP
* provider with network shield or wifi module. Just need to add relevant code to gettime function.
*
* V1.22b 
* 
* Added word clock from particle photon version & adjusted so bottom line is displayed correctly.
* Reduced maximum brightness - e.g changed all 7's to 6's. Was too bright and reduced current draw by about 100mA when displaying pong.
* Added a quick display off button which is on pin 4. 
* Added temperature reading display in big text after date is shown.
* Added separate 7 segment time display that displays when the LED matrix is off. The matrix automatically turns off between 11pm and 6am. 
* 
* V1.21b
* 
* Fixed issues with depreciated constants causing warnings during compilation on latest IDE (1.8.1) / AVR-GCC compiler
* 
* V1.20b
* 
* Beta version - all appears to work OK, needs testing   
* Compiles OK but with depreciated warnings on latest IDE - needs to be investigated. However compiles without issues in Atmel Studio
*      
* Done:-
* 
* Replaced word clock with basic clock - now the alt_clk function. 
* Re-added gettime function
* Removed word clock code.
* Tested birthday code - works OK
* Removed Spectrum Analyzer code
* Removed Ethernet code
* Removed Weather code
* Fixed menu selections selecting wrong function
* Increased menu timeout
* Random mode now works when selected from menu
* Stopped animations from running after selecting a mode from menu which was preventing being able to set time
* Removed SD card reader code (this was using nearly 1Kb of RAM!)
* Changed to bi colour instead of RGB 
* Added scrolling welcome text and version info at start up before pac man animation
* Modified birthday (0), disabled others from being called so can add back later if needed 
* Modified menu to remove option for spectrum analyzer mode. Prevented function from being called in random mode.
* Changed all blue only colours to yellow (green if using RGB matrix) 
* Modified font header files so they will compile on the latest IDE / AVR-GCC compiler. Const required for all PROGMEM variables.
* Put older version of button library in same folder as new version breaks this code.
*/

#include <avr/pgmspace.h>
#include <SPI.h>
#include <TimeLib.h>
#include <Adafruit_GFX.h>   // Core graphics library
#include <RGBmatrixPanel.h> // Hardware-specific library
#include <Time.h>
#include <Wire.h>  
#include <DS1307RTC.h>  // a basic DS1307 library that returns time as a time_t
#include "Button.h"
#include <EEPROM.h>
#include "blinky.h"
#include "font3x5.h"
#include "font5x5.h"
#include "SevenSeg.h"


#define CLK 12  //Used to be 50!!!  // MUST be on PORTB! //used to work on 11
#define LAT A3
#define OE  8  //used to be 51!!
#define A   A4
#define B   A1
#define C   A2

#define ADC_CHANNEL 0
#define F2(progmem_ptr) (const __FlashStringHelper *)progmem_ptr

#define BAT1_X 2                         // Pong left bat x pos (this is where the ball collision occurs, the bat is drawn 1 behind these coords)
#define BAT2_X 28        

#define SHOWCLOCK 8000  

#define MAX_CLOCK_MODE 6                 // Number of clock modes (0 is counted as a mode)

#define X_MAX 31                         // Matrix X max LED coordinate (for 2 displays placed next to each other)
#define Y_MAX 15 

#define DS3231_I2C_ADDR             0x68 // Used to get temperature from internal DS3231 sensor
#define DS3231_TEMPERATURE_ADDR     0x11 // Used to get temperature from internal DS3231 sensor

/*---------7 Segment 2 digit display--------*/
//Defines the segments A-G: SevenSeg(A, B, C, D, E, F, G, DP);
SevenSeg disp (37,36,35,34,33,32,31,30); //(port C)
//Number of 7 segments
const int numOfDigits =4;
//CC(or CA) pins of segment
int digitPins [numOfDigits]={49,48,47,46}; // (port L)

char buffer[512];

long j=0;
String message="";

// Last parameter = 'true' enables double-buffering, for flicker-free,
// buttery smooth animation.  Note that NOTHING WILL SHOW ON THE DISPLAY
// until the first call to swapBuffers().  This is normal.
RGBmatrixPanel matrix(A, B, C, CLK, LAT, OE, true);

Button buttonA = Button(2,BUTTON_PULLUP_INTERNAL);       // Setup button A (using button library)
Button buttonB = Button(3,BUTTON_PULLUP_INTERNAL);       // Setup button B (using button library)
Button buttonC = Button(4,BUTTON_PULLUP_INTERNAL);       // Setup button C (using button library)

int mode_changed = 0;                    // Flag if mode changed.
int clock_mode = 1;                      // Default clock mode (1 = pong) (was 0) Should stay in this mode unless random is selected

int random_mode = 0; 
int mode_time_up;   
int dispoff = 0; 

int powerPillEaten = 0;

const char str[] PROGMEM = "Arcade Game Clock Version 1.23";
int    textX   = matrix.width(),
textMin = sizeof(str) * -12;
int    textX2   = matrix.width(),
textMin2 = sizeof(message) * -12;
long   hue     = 0;

void setup() {
  
  matrix.begin();
  matrix.setTextWrap(false); // Allow text to run off right edge
  matrix.setTextSize(1);
  matrix.setTextColor(matrix.Color333(210, 210, 210));

  delay(1000);

  Serial.begin(9600);

  delay(500);

  while(!Serial);
  setSyncProvider(RTC.get);   // the function to get the time from the RTC
  if(timeStatus()!= timeSet) 
    Serial.println("Unable to sync with the RTC");
  else
    Serial.println("RTC has set the system time");      

   // LED 7 segment display
   disp.setDigitPins ( numOfDigits , digitPins );
   disp.setCommonAnode();
   disp.setDPPin(30);
   disp.setSymbPins (45,37,36,35); // setSymbPins(int digPin, int segUCPin, int segLCPin, int segAPin); // segA is apostrophe
   disp.setDutyCycle(100);
   disp.setRefreshRate(175); // multiplex rate in hz. Divide peak current by num of displays.
   // e.g if current limiting resistors are selected for 20mA of current per segment and you have 4 digits then average current per LED
   // segment will be 5mA. In most cases this results in a dim display so lower current limiting resistor values or use LED driver chip.
   // with using the display from the Humax box the current limiting resistors need to be 47 ohms (3.2/47 = 68mA / 5 digits = 13mA) but
   // in reality it will be less than this as the refresh rate has been increased to reduce flicker. Average current per segment = 10mA.
   // Ensure total current does not exceed pin current limit. 

  mode_time_up = hour();
  byte i;
  scrollIntro();
  pacMan();  
  }



void loop(){
  
    if (random_mode){  
    gettime();
    //set counter to change clock type every 3 or 4 hours
    if (mode_time_up == hour()) {
      mode_time_up = hour() + random (2,4);   //set next time to change - add 2 to 3 hours
      if (mode_time_up >= 24) { 
        mode_time_up = random (1,2); 
      }   //if time is over 24, set to 0 + random
      clock_mode = random(0,MAX_CLOCK_MODE - 1);     //pick new random mode
    }
  }

  //reset clock type clock_mode
  switch (clock_mode){
  case 0: 
    normal_clock(); // (numbers)
    break; 
  case 1: 
    pong(); 
    break;
  case 2: 
    alt_clk();     // (Time and day)
    break;
  case 3: 
    jumble(); 
    break; 
  case 4:
    word_clock();  
  case 5: random_mode = 1; // second to last mode has to be random, if adding new mode add it lower down in the list increase max_mode
    break;
  case 6: // last mode has to be set clock
    set_time(); 
    break;
   
    }

  //if the mode hasn't changed, show the date
  if (mode_changed == 0) { 
    display_date(); 
    display_temp();
    pacClear();
     } 
  else {
    //the mode has changed, so don't bother showing the date, just go to the new mode.
    mode_changed = 0; //reset mode flag.
  }

}

// ***** End of main loop *****

// Functions

float DS3231_get_treg()
{
    short rv;  
    
    Wire.beginTransmission(DS3231_I2C_ADDR);
    Wire.write(DS3231_TEMPERATURE_ADDR);
    Wire.endTransmission();

    Wire.requestFrom(DS3231_I2C_ADDR, 2);
    rv = Wire.read() << 8;
    rv |= Wire.read();
  return(rv/256.);
}

void gettime()
{
  setSyncProvider(RTC.get);   // the function to get the time from the RTC 
  if(timeStatus()!= timeSet) 
    Serial.println("Unable to sync with the RTC");
  else
    Serial.println("RTC has set the system time");      
}

void display_off() 

{
  //loop to display the clock for a set duration of SHOWCLOCK
  for (int show = 0; show < SHOWCLOCK ; show) // don't increment show here to keep in an infitite loop until button pressed.
  {
     // code here to display time on 7 segment display

    //gettime();
    PORTB &= ~(1<<PORTB7); // Turn built in LED off (pin 13)
    char c = ':' ;
    //disp.writeClock((hour()), (minute()), c); // use this to set colon on all the time
    disp.writeClock( (hour()), (minute()) ); // use this line in conjunction with below function to flash colon
            
      if ( (second() % 2) == 0) 
      { 
        disp.setColon();
      }
      
    if(buttonA.uniquePress()){
      switch_mode();
      dispoff = 0; // reset display off flag so if button pressed does not immediately turn the matrix off again. 
      return;
    }

    if(buttonB.uniquePress()){
      display_date();
      display_temp();
      display_off();
      return;
    }

    if (hour() == 06 && minute() == 00 && second() == 00)
    {
      dispoff = 0;
      switch_mode();
      return;
    }    
       
    matrix.fillScreen(0);     
    matrix.swapBuffers(false);
   
  }
}

void alt_clk ()
{
  matrix.setTextSize(1);
  matrix.fillScreen(0);
  uint16_t color = matrix.Color333(0,6,0);
   //read the date from the DS1307
  //it returns the month number, day number, and a number representing the day of week - 1 for Tue, 2 for Wed 3 for Thu etc.
  byte dow = weekday()-1; //rtc[3] - 1; //we  take one off the value the DS1307 generates, as our array of days is 0-6 and the DS1307 outputs  1-7.
  byte date = day();  //rtc[4];
  byte mont = month()-1; //rtc[5] - 1; 

  //array of day and month names to print on the display. Some are shortened as we only have 8 characters across to play with 
  char daynames[7][9]={
    "Sunday", "Monday","Tuesday", "Wed", "Thursday", "Friday", "Saturday"                  };
  char monthnames[12][9]={
    "January", "February", "March", "April", "May", "June", "July", "August", "Sept", "October", "November", "December"                  };
  
    //loop to display the clock for a set duration of SHOWCLOCK
  for (int show = 0; show < SHOWCLOCK ; show++) {

    //gettime(); //get the time from the clock chip

    if(buttonA.uniquePress()){
      switch_mode();
      return;
    }
    if(buttonB.uniquePress()){
      display_date();
      display_temp();
      alt_clk();
      return;
    }
     if(buttonC.uniquePress()){
      display_off();
      return;
    }

     if ( (second() % 2) == 0) 
    { 
      PORTB |= (1<<PORTB7); // Turn built in LED on (pin 13)
    }
    else
    { 
      PORTB &= ~(1<<PORTB7); // Turn built in LED off (pin 13) 
    } 

 if (hour() == 23 && minute() == 00 && second() == 00)
    dispoff = 1;

    if (dispoff == 1)
    {
      display_off();
    }

 if (hour() == 12 && minute() == 00 && second() == 00)

    {
       gettime();
    }

//Display time
   
   matrix.fillScreen(0);
   matrix.setCursor(2, 0);   
   matrix.setTextColor(matrix.Color333(6, 0, 0));
   if(hour()<10) matrix.print(0, DEC);
   matrix.print(hour(), DEC);
   matrix.print(':');
   if(minute()<10) matrix.print(0, DEC); 
   matrix.print(minute(), DEC);
   matrix.setCursor(7, 9);
   matrix.setTextColor(matrix.Color333(0, 6, 0));
  int i = 0; 
  while(daynames[dow][i])
  {
  drawChar(i*4,9,daynames[dow][i],51,color); 
  i++;
  }
     
   matrix.swapBuffers(false);
   
}
}

 
void scrollIntro ()
{ // used for intro message 

  for(int i = 64; i>-300; i--)
  {
    cls();
    matrix.setCursor(textX,1);
    matrix.setTextSize(1);
    matrix.setTextColor(matrix.Color333(6, 4, 0 ));
    matrix.print(F2(str));
    delay(15);
    if((--textX) < textMin) textX = matrix.width();
    matrix.swapBuffers(false); 
    
   }

}

//Runs pacman or other animation
void pacClear(){
 
  {     
    //Yuk - hard coded special days - should have done this differently
    if(month()==10 && day()==28){
      birthday(0);
    }
    /*
    else if(month()==3 && day()==3){
      birthday(1);
    }
    else if(month()==5 && day()==27){
      birthday(2);
    }
    else if(month()==2 && day()==25){
      birthday(3);
    }
    */
    else if(month()==10 && day()>27){
      halloween();
    }
    else if(month()==12 && day()>20){
      christmas();
    }
    else if(month()==1 && day()<3){
      christmas();
    }
    else{
      pacMan();
    }
  }
}  

void halloween(){
  long starttime = millis();
  if(random(0,4)==2){
    while(millis()<starttime+5000){
      cls();
      if(random(0,30)==10)matrix.drawBitmap(0,0,boo,32,16,matrix.Color444(6,6,0));
      matrix.swapBuffers(true);
    }
  }
  else{
    while(millis()<starttime+5000){
      cls();
      matrix.drawBitmap(0,0,pumpkin_body,32,16,matrix.Color444(6,3,0));
      matrix.drawBitmap(0,0,pumpkin_top,32,16,matrix.Color444(0,6,0));
      //if(1==0)matrix.drawBitmap(0,0,pumpkin_ribs,32,16,matrix.Color444(0,0,0));
      matrix.drawBitmap(0,0,pumpkin_face,32,16,matrix.Color444(1,1,0));
      if(random(0,10)==3) matrix.drawBitmap(0,0,pumpkin_face,32,16,matrix.Color444(6,6,random(0,5)));
      matrix.swapBuffers(true);    
    }
  }
}

void christmas(){
  int xpos[11] = {6, 8, 8, 5, 13, 10, 9, 6, 7, 11,2};
  int ypos[11] = {1, 2, 5, 6, 8,   8, 9, 9, 11,12,13};
  long starttime = millis();
  int mode = random(0,3);
  //int mode =1;
  if(mode==0){
  	//Draw a tree
    int pos=-16;
    while(millis()<starttime+10000){
      cls();
      matrix.drawBitmap(0+pos,0, tree ,16,16,matrix.Color444(0,2,0));
      matrix.fillRect(7+pos,14,2,2,matrix.Color444(6,3,0));
      int lightColor = matrix.Color444(0,0,6);
      if(millis()/1000 %2) lightColor = matrix.Color444(6,4,0);
      for(int a=0;a<11;a++){
        matrix.drawPixel(xpos[a]+pos,ypos[a],lightColor);
      }
      matrix.swapBuffers(true);
      pos++;
      delay(200);
    }
  }
  else if(mode==1){
  	//Ho Ho Ho
    while(millis()<starttime+10000){
       int xpos=random(0,31);
       int ypos=random(0,15);
       int size=random(0,2);
       matrix.fillRect(xpos-1,ypos-1,(size==0?9:13),7,matrix.Color444(0,0,0));
       drawString(xpos,ypos,"HO",(size==0?51:53),matrix.Color444(random(6,6),random(6,6),random(6,6)));    
   
       matrix.swapBuffers(true);
       delay(100);
    }
  }
  else{
  	//scrolling present
    int pos=-16;
    while(millis()<starttime+10000){
      cls();
      matrix.fillRect(2+pos,5,12,10,matrix.Color444(1,0,3));
      matrix.drawBitmap(0+pos,0, bow ,16,16,matrix.Color444(4,4,0));
      matrix.swapBuffers(true);
      pos++;
      delay(200);
    }
  }
}

int freeRam () {
  extern int __heap_start, *__brkval; 
  int v; 
  return (int) &v - (__brkval == 0 ? (int) &__heap_start : (int) __brkval); 
}

void pacMan(){

  if(powerPillEaten>0){
    for(int i =32+(powerPillEaten*17); i>-17; i--){
      long nowish = millis();
      cls();

      drawPac(i,0,-1);
      if(powerPillEaten>0) drawScaredGhost(i-17,0);
      if(powerPillEaten>1) drawScaredGhost(i-34,0);
      if(powerPillEaten>2) drawScaredGhost(i-51,0);
      if(powerPillEaten>3) drawScaredGhost(i-68,0);

      matrix.swapBuffers(false);    
      while(millis()-nowish<50);  
    }
    powerPillEaten = 0;
  }
  else{  

    int hasEaten = 0;

    int powerPill = random(0,5);
    int numGhosts=random(0,4);
    if(powerPill ==0){
      if(numGhosts==0) numGhosts++;
      powerPillEaten = numGhosts;

    }

    for(int i=-17; i<32+(numGhosts*17); i++){
      cls();
      long nowish = millis();
      for(int j = 0; j<6;j++){

        if( j*5> i){

          if(powerPill==0 && j==4){
            matrix.fillCircle(j*5,8,2,matrix.Color333(6,3,0));

          }
          else{
            matrix.fillRect(j*5,8,2,2,matrix.Color333(6,3,0));
          }
        }

      }

      if(i==19 && powerPill == 0) hasEaten=1;

      drawPac(i,0,1);
      if(hasEaten == 0){
        if(numGhosts>0) drawGhost(i-17,0,matrix.Color333(3,5,0));
        if(numGhosts>1) drawGhost(i-34,0,matrix.Color333(5,1,0));
        if(numGhosts>2) drawGhost(i-51,0,matrix.Color333(3,3,0));
        if(numGhosts>3) drawGhost(i-68,0,matrix.Color333(1,5,0));
      }
      else{
        if(numGhosts>0) drawScaredGhost(i-17-(i-19)*2,0);
        if(numGhosts>1) drawScaredGhost(i-34-(i-19)*2,0);
        if(numGhosts>2) drawScaredGhost(i-51-(i-19)*2,0);
        if(numGhosts>3) drawScaredGhost(i-68-(i-19)*2,0);

      }

      matrix.swapBuffers(false);
      while(millis()-nowish<50);
    }
  }
}

void drawPac(int x, int y, int z){
  int c = matrix.Color333(3,3,0);
  if(x>-16 && x<32){
    if(abs(x)%4==0){
      matrix.drawBitmap(x,y,(z>0?pac:pac_left),16,16,c);
    }
    else if(abs(x)%4==1 || abs(x)%4==3){
      matrix.drawBitmap(x,y,(z>0?pac2:pac_left2),16,16,c);
    }
    else{
      matrix.drawBitmap(x,y,(z>0?pac3:pac_left3),16,16,c);
    }
  }
}



void drawGhost( int x, int y, int color){
  if(x>-16 && x<32){
    if(abs(x)%8>3){
      matrix.drawBitmap(x,y,blinky,16,16,color);
    }
    else{
      matrix.drawBitmap(x,y,blinky2,16,16,color);
    }
    matrix.drawBitmap(x,y,eyes1,16,16,matrix.Color333(0,6,0));
    matrix.drawBitmap(x,y,eyes2,16,16,matrix.Color333(6,0,0));
  }
}  

void drawScaredGhost( int x, int y){
  if(x>-16 && x<32){
    if(abs(x)%8>3){
      matrix.drawBitmap(x,y,blinky,16,16,matrix.Color333(1,1,0));
    }
    else{
      matrix.drawBitmap(x,y,blinky2,16,16,matrix.Color333(4,6,0));
    }
    matrix.drawBitmap(x,y,scared,16,16,matrix.Color333(6,3,0));
  }
}  


void birthday(int who){

  String person="";     
  switch(who){
  case 0:
    person = "ADRIAN";
    break;
  case 1:
    person = "NOT_USED";
    break;
  case 2:
    person = "NOT_USED";
    break;
  case 3        :
    person = "NOT_USED";
    break;
  }

  //Slide Happy Birthday in from right
  for(int i = 0;i<32; i++){
    cls();
    drawString(32-(i<16?i*2:31),1,"HAPPY",53,matrix.Color444(15-(i/2),1,(i/2)));
    drawString(32-i,8,"BIRTHDAY",51,matrix.Color444((i/2),1,15-(i/2))); 
    matrix.swapBuffers(false);
    delay(20);  
  }
  //Change Colours 
  for(int a = 0;a<16; a++){
    cls();
    drawString(1,1,"HAPPY",53,matrix.Color444((a>7?7:a),1,(15-a<7?7:15-a)));
    drawString(1,8,"BIRTHDAY",51,matrix.Color444( ( (15-a) < 7 ? 7: (15-a) ) ,1, (a>7 ? 7 :a) ) ); 
    matrix.swapBuffers(false);
    delay(20);  
  }
  delay(1000);
  //Slide Happy Birthday down off screen
  delay(1000);
  for(int a = 1;a<17; a++){
    cls();
    drawString(1,a,"HAPPY",53,matrix.Color444((a>7?7:a),1,(15-a<7?7:15-a)));
    drawString(1,7+a,"BIRTHDAY",51,matrix.Color444( ( (15-a) < 7 ? 7: (15-a) ) ,1, (a>7 ? 7 :a) ) ); 
    matrix.swapBuffers(false);
    delay(20);  
  };

  //Slide Cake up
  for(int i=17;i>-1;i--){
    cls();
    drawCake(0,i);
    matrix.swapBuffers(true);
  }


  //Background cake, scroll name

    for(int i=64; i>-200; i--){
    cls();
    drawCake(0,0);

    matrix.setCursor(i,1);
    matrix.setTextColor(matrix.Color444(0,15,0));
    matrix.setTextSize(2);
    matrix.setTextWrap(false);
    matrix.print(person);

    matrix.swapBuffers(true);

    delay(20);   
  }

  //Slide Cake up
  for(int i=0;i>-17;i--){
    cls();
    drawCake(0,i);
    matrix.swapBuffers(true);
  }


}

void drawCake(int x, int y){
  cls();
  matrix.drawBitmap(x,y,cake_main,32,16,matrix.Color444(4,1,0));  
  matrix.drawBitmap(x,y,cake_top,32,16,matrix.Color444(6,3,2));  
  matrix.drawBitmap(x,y,cake_cream,32,16,matrix.Color333(6,6,6));  
  matrix.drawBitmap(x,y,cake_cherries,32,16,matrix.Color333(6,0,0));  
  matrix.drawBitmap(x,y,cake_candle,32,16,matrix.Color333(0,2,2));  
  int r = random(0,5);
  int c = random(4,15);
  int col = matrix.Color444(c,c-3,0);
  switch(r){
  case 0:
    matrix.drawLine(16+x,y,16+x,2+y,col);
    break;
  case 1:      
    matrix.drawLine(15+x,y,16+x,2+y,col);
    break;
  case 2:      
    matrix.drawLine(17+x,y,16+x,2+y,col);
    break;
  case 3:      
    matrix.drawLine(15+x,y,16+x,2+y,col);
    break;
  case 4:      
    matrix.fillTriangle(15+x,y,16+x,2+y,15+x,2+y,col);
    break;
  case 5:      
    matrix.fillTriangle(17+x,y,16+x,2+y,17+x,2+y,col);
    break;
  }
}

void cls()
{
  matrix.fillScreen(0);
}

void pong(){
  matrix.setTextSize(1);
  matrix.setTextColor(matrix.Color333(6, 6, 0));

  float ballpos_x, ballpos_y;
  byte erase_x = 10;  //holds ball old pos so we can erase it, set to blank area of screen initially.
  byte erase_y = 10;
  float ballvel_x, ballvel_y;
  int bat1_y = 5;  //bat starting y positions
  int bat2_y = 5;  
  int bat1_target_y = 5;  //bat targets for bats to move to
  int bat2_target_y = 5;
  byte bat1_update = 1;  //flags - set to update bat position
  byte bat2_update = 1;
  byte bat1miss, bat2miss; //flags set on the minute or hour that trigger the bats to miss the ball, thus upping the score to match the time.
  byte restart = 1;   //game restart flag - set to 1 initially to setup 1st game


  cls();

  for(int i=0; i< SHOWCLOCK; i++) {
    cls();
    //draw pitch centre line
    int adjust = 0;
    if(second()%2==0)adjust=1;
    for (byte i = 0; i <16; i++) {

      if ( i % 2 == 0 ) { //plot point if an even number

        matrix.drawPixel(16,i+adjust,matrix.Color333(0,5,0));
      }
    } 

    //main pong game loop

    //flash led for seconds on arduino
    if ( (second() % 2) == 0) 
    {       
      PORTB |= (1<<PORTB7);
    }
    else
    { 
      PORTB &= ~(1<<PORTB7);
    }

    //check buttons  
    if(buttonA.uniquePress()){
      switch_mode();   //switch display mode.
      return;
    }
    if(buttonB.uniquePress()){
      display_date();
      display_temp();
      pong();
      return;
    }

    if(buttonC.uniquePress()){
      display_off();
      return;
    }

    if (hour() == 23 && minute() == 00 && second() == 00)
    dispoff = 1;

    if (dispoff == 1)
    {
      display_off();
    }

   if (hour() == 12 && minute() == 00 && second() == 00)

    {
       gettime();
    }

    int ampm=0;
    //update score / time
    byte mins = minute();
    byte hours = hour();
    if (hours > 12) {
      hours = hours - ampm * 12;
    }
    if (hours < 1) {
      hours = hours + ampm * 12;
    }

    char buffer[3];

    itoa(hours,buffer,10);
    //fix - as otherwise if num has leading zero, e.g. "03" hours, itoa coverts this to chars with space "3 ". 
    if (hours < 10) {
      buffer[1] = buffer[0];
      buffer[0] = '0';
    }
    
    vectorNumber(buffer[0]-'0',8,1,matrix.Color333(6,6,1),1,1);
    vectorNumber(buffer[1]-'0',12,1,matrix.Color333(6,6,1),1,1);
    
    itoa(mins,buffer,10); 
    if (mins < 10) {
      buffer[1] = buffer[0];
      buffer[0] = '0';
    } 
    
    vectorNumber(buffer[0]-'0',18,1,matrix.Color333(6,6,1),1,1);
    vectorNumber(buffer[1]-'0',22,1,matrix.Color333(6,6,1),1,1);
   
    //if restart flag is 1, setup a new game
    if (restart) {

      //set ball start pos
      ballpos_x = 16;
      ballpos_y = random (4,12);

      //pick random ball direction
      if (random(0,2) > 0) {
        ballvel_x = 1; 
      } 
      else {
        ballvel_x = -1;
      }
      if (random(0,2) > 0) {
        ballvel_y = 0.5; 
      } 
      else {
        ballvel_y = -0.5;
      }
      //draw bats in initial positions
      bat1miss = 0; 
      bat2miss = 0;
      //reset game restart flag
      restart = 0;

      //short wait for effect
      //delay(500);
    }

    //get the time from the rtc
    //gettime();

    //if coming up to the minute: secs = 59 and mins < 59, flag bat 2 (right side) to miss the return so we inc the minutes score
    if (second() == 59 && minute() < 59){
      bat1miss = 1;
    }
    // if coming up to the hour: secs = 59  and mins = 59, flag bat 1 (left side) to miss the return, so we inc the hours score.
    if (second() == 59 && minute() == 59){
      bat2miss = 1;
    }


    //AI - we run 2 sets of 'AI' for each bat to work out where to go to hit the ball back 

    //very basic AI...
    // For each bat, First just tell the bat to move to the height of the ball when we get to a random location.
    //for bat1
    if (ballpos_x == random(18,32)){// && ballvel_x < 0) {
      bat1_target_y = ballpos_y;
    }
    //for bat2
    if (ballpos_x == random(4,16)){//  && ballvel_x > 0) {
      bat2_target_y = ballpos_y;
    }

    //when the ball is closer to the left bat, run the ball maths to find out where the ball will land
    if (ballpos_x == 15 && ballvel_x < 0) {

      byte end_ball_y = pong_get_ball_endpoint(ballpos_x, ballpos_y, ballvel_x, ballvel_y);

      //if the miss flag is set,  then the bat needs to miss the ball when it gets to end_ball_y
      if (bat1miss == 1){
        bat1miss = 0;
        if ( end_ball_y > 8){
          bat1_target_y = random (0,3); 
        } 
        else {
          bat1_target_y = 8 + random (0,3);              
        }      
      } 
      //if the miss flag isn't set,  set bat target to ball end point with some randomness so its not always hitting top of bat
      else {
        bat1_target_y = end_ball_y - random (0, 6);        
        //check not less than 0
        if (bat1_target_y < 0){
          bat1_target_y = 0;
        }
        if (bat1_target_y > 10){
          bat1_target_y = 10;
        } 
      }
    }


    //right bat AI
    //if positive velocity then predict for right bat - first just match ball height

    //when the ball is closer to the right bat, run the ball maths to find out where it will land
    if (ballpos_x == 17 && ballvel_x > 0) {

      byte end_ball_y = pong_get_ball_endpoint(ballpos_x, ballpos_y, ballvel_x, ballvel_y);

      //if flag set to miss, move bat out way of ball
      if (bat2miss == 1){
        bat2miss = 0;
        //if ball end point above 8 then move bat down, else move it up- so either way it misses
        if (end_ball_y > 8){
          bat2_target_y = random (0,3); 
        } 
        else {
          bat2_target_y = 8 + random (0,3);
        }      
      } 
      else {
        //set bat target to ball end point with some randomness 
        bat2_target_y =  end_ball_y - random (0,6);
        //ensure target between 0 and 15
        if (bat2_target_y < 0){
          bat2_target_y = 0;
        } 
        if (bat2_target_y > 10){
          bat2_target_y = 10;
        } 
      }
    }


    //move bat 1 towards target    
    //if bat y greater than target y move down until hit 0 (dont go any further or bat will move off screen)
    if (bat1_y > bat1_target_y && bat1_y > 0 ) {
      bat1_y--;
      bat1_update = 1;
    }

    //if bat y less than target y move up until hit 10 (as bat is 6)
    if (bat1_y < bat1_target_y && bat1_y < 10) {
      bat1_y++;
      bat1_update = 1;
    }

    //draw bat 1
    if (bat1_update){
      matrix.fillRect(BAT1_X-1,bat1_y,2,6,matrix.Color333(4,0,0));
      
    }


    //move bat 2 towards target (dont go any further or bat will move off screen)

    //if bat y greater than target y move down until hit 0
    if (bat2_y > bat2_target_y && bat2_y > 0 ) {
      bat2_y--;
      bat2_update = 1;
    }

    //if bat y less than target y move up until hit max of 10 (as bat is 6)
    if (bat2_y < bat2_target_y && bat2_y < 10) {
      bat2_y++;
      bat2_update = 1;
    }

    //draw bat2
    if (bat2_update){
      matrix.fillRect(BAT2_X+1,bat2_y,2,6,matrix.Color333(0,4,0));
     
    }

    //update the ball position using the velocity
    ballpos_x =  ballpos_x + ballvel_x;
    ballpos_y =  ballpos_y + ballvel_y;

    //check ball collision with top and bottom of screen and reverse the y velocity if either is hit
    if (ballpos_y <= 0 ){
      ballvel_y = ballvel_y * -1;
      ballpos_y = 0; //make sure value goes no less that 0
    }

    if (ballpos_y >= 15){
      ballvel_y = ballvel_y * -1;
      ballpos_y = 15; //make sure value goes no more than 15
    }

    //check for ball collision with bat1. check ballx is same as batx
    //and also check if bally lies within width of bat i.e. baty to baty + 6. We can use the exp if(a < b && b < c) 
    if ((int)ballpos_x == BAT1_X+1 && (bat1_y <= (int)ballpos_y && (int)ballpos_y <= bat1_y + 5) ) { 

      //random if bat flicks ball to return it - and therefor changes ball velocity
      if(!random(0,3)) { //not true = no flick - just straight rebound and no change to ball y vel
        ballvel_x = ballvel_x * -1;
      } 
      else {
        bat1_update = 1;
        byte flick;  //0 = up, 1 = down.

        if (bat1_y > 1 || bat1_y < 8){
          flick = random(0,2);   //pick a random dir to flick - up or down
        }

        //if bat 1 or 2 away from top only flick down
        if (bat1_y <=1 ){
          flick = 0;   //move bat down 1 or 2 pixels 
        } 
        //if bat 1 or 2 away from bottom only flick up
        if (bat1_y >=  8 ){
          flick = 1;  //move bat up 1 or 2 pixels 
        }

        switch (flick) {
          //flick up
        case 0:
          bat1_target_y = bat1_target_y + random(1,3);
          ballvel_x = ballvel_x * -1;
          if (ballvel_y < 2) {
            ballvel_y = ballvel_y + 0.2;
          }
          break;

          //flick down
        case 1:   
          bat1_target_y = bat1_target_y - random(1,3);
          ballvel_x = ballvel_x * -1;
          if (ballvel_y > 0.2) {
            ballvel_y = ballvel_y - 0.2;
          }
          break;
        }
      }
    }


    //check for ball collision with bat2. check ballx is same as batx
    //and also check if bally lies within width of bat i.e. baty to baty + 6. We can use the exp if(a < b && b < c) 
    if ((int)ballpos_x == BAT2_X && (bat2_y <= (int)ballpos_y && (int)ballpos_y <= bat2_y + 5) ) { 

      //random if bat flicks ball to return it - and therefor changes ball velocity
      if(!random(0,3)) {
        ballvel_x = ballvel_x * -1;    //not true = no flick - just straight rebound and no change to ball y vel
      } 
      else {
        bat1_update = 1;
        byte flick;  //0 = up, 1 = down.

        if (bat2_y > 1 || bat2_y < 8){
          flick = random(0,2);   //pick a random dir to flick - up or down
        }
        //if bat 1 or 2 away from top only flick down
        if (bat2_y <= 1 ){
          flick = 0;  //move bat up 1 or 2 pixels 
        } 
        //if bat 1 or 2 away from bottom only flick up
        if (bat2_y >=  8 ){
          flick = 1;   //move bat down 1 or 2 pixels 
        }

        switch (flick) {
          //flick up
        case 0:
          bat2_target_y = bat2_target_y + random(1,3);
          ballvel_x = ballvel_x * -1;
          if (ballvel_y < 2) {
            ballvel_y = ballvel_y + 0.2;
          }
          break;

          //flick down
        case 1:   
          bat2_target_y = bat2_target_y - random(1,3);
          ballvel_x = ballvel_x * -1;
          if (ballvel_y > 0.2) {
            ballvel_y = ballvel_y - 0.2;
          }
          break;
        }
      }
    }

    //plot the ball on the screen
    byte plot_x = (int)(ballpos_x + 0.5f);
    byte plot_y = (int)(ballpos_y + 0.5f);

    matrix.drawPixel(plot_x,plot_y,matrix.Color333(4, 0, 0));

    //check if a bat missed the ball. if it did, reset the game.
    if ((int)ballpos_x == 0 ||(int) ballpos_x == 32){
      restart = 1; 
    } 
    delay(40);
    matrix.swapBuffers(false);
  } 
  //fade_down();
}
byte pong_get_ball_endpoint(float tempballpos_x, float  tempballpos_y, float  tempballvel_x, float tempballvel_y) {

  //run prediction until ball hits bat
  while (tempballpos_x > BAT1_X && tempballpos_x < BAT2_X  ){     
    tempballpos_x = tempballpos_x + tempballvel_x;
    tempballpos_y = tempballpos_y + tempballvel_y;
    //check for collisions with top / bottom
    if (tempballpos_y <= 0 || tempballpos_y >= 15){
      tempballvel_y = tempballvel_y * -1;
    }    
  }  
  return tempballpos_y; 
}

void normal_clock()
{
  matrix.setTextWrap(false); // Allow text to run off right edge
  matrix.setTextSize(2);
  matrix.setTextColor(matrix.Color333(6, 0, 0));

  cls();
  byte hours = hour();
  byte mins = minute();

  int  msHourPosition = 0;
  int  lsHourPosition = 0;
  int  msMinPosition = 0;
  int  lsMinPosition = 0;      
  int  msLastHourPosition = 0;
  int  lsLastHourPosition = 0;
  int  msLastMinPosition = 0;
  int  lsLastMinPosition = 0;      

  //Start with all characters off screen
  int c1 = -17;
  int c2 = -17;
  int c3 = -17;
  int c4 = -17;

  float scale_x =2.5;
  float scale_y =3.0;


  char lastHourBuffer[3]="  ";
  char lastMinBuffer[3] ="  ";

  //loop to display the clock for a set duration of SHOWCLOCK

  for (int show = 0; show < SHOWCLOCK ; show++) {

    cls();

    //gettime(); //get the time from the clock chip

    //flash led for seconds on arduino
    if ( (second() % 2) == 0) 
    { 
      PORTB |= (1<<PORTB7); // Turn built in LED on (pin 13)
    }
    else
    { 
      PORTB &= ~(1<<PORTB7); // Turn built in LED off (pin 13) 
    } 

    //check buttons  
    if(buttonA.uniquePress()){
      switch_mode();
      //pacClear();
      return;      
    }
    if(buttonB.uniquePress()){
      display_date();
      display_temp();
      normal_clock();
      return;
    }

    if(buttonC.uniquePress()){
      display_off();
      return;
    }

 if (hour() == 23 && minute() == 00 && second() == 00)
    dispoff = 1;

    if (dispoff == 1)
    {
      display_off();
    }

 if (hour() == 12 && minute() == 00 && second() == 00)

    {
       gettime();
    }

    //update mins and hours with the new time
    mins = minute();
    hours = hour();

    char buffer[3];

    itoa(hours,buffer,10);
    //fix - as otherwise if num has leading zero, e.g. "03" hours, itoa coverts this to chars with space "3 ". 
    if (hours < 10) {
      buffer[1] = buffer[0];
      buffer[0] = '0';
    }

    if(lastHourBuffer[0]!=buffer[0] && c1==0) c1= -17;
    if( c1 < 0 )c1++;
    msHourPosition = c1;
    msLastHourPosition = c1 + 17;

    if(lastHourBuffer[1]!=buffer[1] && c2==0) c2= -17;
    if( c2 < 0 )c2++;
    lsHourPosition = c2;
    lsLastHourPosition = c2 + 17;

    //update the display

    vectorNumber((lastHourBuffer[0]-'0'), 1, 1+msLastHourPosition, matrix.Color444(6,6,0),scale_x,scale_y);
    vectorNumber((lastHourBuffer[1]-'0'), 8, 1+lsLastHourPosition, matrix.Color444(6,6,0),scale_x,scale_y);
    vectorNumber((buffer[0]-'0'), 1, 1+msHourPosition, matrix.Color444(6,6,0),scale_x,scale_y);
    vectorNumber((buffer[1]-'0'), 8, 1+lsHourPosition, matrix.Color444(6,6,0),scale_x,scale_y);    


    if(c1==0) lastHourBuffer[0]=buffer[0];
    if(c2==0) lastHourBuffer[1]=buffer[1];

    matrix.fillRect(16,5,2,2,matrix.Color444(0,0,second()%2));
    matrix.fillRect(16,11,2,2,matrix.Color444(0,0,second()%2));

    matrix.fillRect(15,4,2,2,matrix.Color444(second()%2,second()%2,second()%2));
    matrix.fillRect(15,10,2,2,matrix.Color444(second()%2,second()%2,second()%2));

    itoa (mins, buffer, 10);
    if (mins < 10) {
      buffer[1] = buffer[0];
      buffer[0] = '0';
    }

    if(lastMinBuffer[0]!=buffer[0] && c3==0) c3= -17;
    if( c3 < 0 )c3++;
    msMinPosition = c3;
    msLastMinPosition= c3 + 17;

    if(lastMinBuffer[1]!=buffer[1] && c4==0) c4= -17;
    if( c4 < 0 )c4++;
    lsMinPosition = c4;
    lsLastMinPosition = c4 + 17;

    vectorNumber((buffer[0]-'0'), 18, 1+msMinPosition, matrix.Color444(6,6,0),scale_x,scale_y); //18
    vectorNumber((buffer[1]-'0'), 25, 1+lsMinPosition, matrix.Color444(6,6,0),scale_x,scale_y); //25
    vectorNumber((lastMinBuffer[0]-'0'), 18, 1+msLastMinPosition, matrix.Color444(6,6,0),scale_x,scale_y);
    vectorNumber((lastMinBuffer[1]-'0'), 25, 1+lsLastMinPosition, matrix.Color444(6,6,0),scale_x,scale_y);


    if(c3==0) lastMinBuffer[0]=buffer[0];
    if(c4==0) lastMinBuffer[1]=buffer[1];

    matrix.swapBuffers(false); 

  }

}

//Draw number n, with x,y as top left corner, in chosen color, scaled in x and y.
//when scale_x, scale_y = 1 then character is 3x5
void vectorNumber(int n, int x, int y, int color, float scale_x, float scale_y){

  switch (n){
  case 0:
    matrix.drawLine(x ,y , x , y+(4*scale_y) , color);
    matrix.drawLine(x , y+(4*scale_y) , x+(2*scale_x) , y+(4*scale_y), color);
    matrix.drawLine(x+(2*scale_x) , y , x+(2*scale_x) , y+(4*scale_y) , color);
    matrix.drawLine(x ,y , x+(2*scale_x) , y , color);
    break; 
  case 1: 
    matrix.drawLine( x+(1*scale_x), y, x+(1*scale_x),y+(4*scale_y), color);  
    matrix.drawLine(x , y+4*scale_y , x+2*scale_x , y+4*scale_y,color);
    matrix.drawLine(x,y+scale_y, x+scale_x, y,color);
    break;
  case 2:
    matrix.drawLine(x ,y , x+2*scale_x , y , color);
    matrix.drawLine(x+2*scale_x , y , x+2*scale_x , y+2*scale_y , color);
    matrix.drawLine(x+2*scale_x , y+2*scale_y , x , y+2*scale_y, color);
    matrix.drawLine(x , y+2*scale_y, x , y+4*scale_y,color);
    matrix.drawLine(x , y+4*scale_y , x+2*scale_x , y+4*scale_y,color);
    break; 
  case 3:
    matrix.drawLine(x ,y , x+2*scale_x , y , color);
    matrix.drawLine(x+2*scale_x , y , x+2*scale_x , y+4*scale_y , color);
    matrix.drawLine(x+2*scale_x , y+2*scale_y , x+scale_x , y+2*scale_y, color);
    matrix.drawLine(x , y+4*scale_y , x+2*scale_x , y+4*scale_y,color);
    break;
  case 4:
    matrix.drawLine(x+2*scale_x , y , x+2*scale_x , y+4*scale_y , color);
    matrix.drawLine(x+2*scale_x , y+2*scale_y , x , y+2*scale_y, color);
    matrix.drawLine(x ,y , x , y+2*scale_y , color);
    break;
  case 5:
    matrix.drawLine(x ,y , x+2*scale_x , y , color);
    matrix.drawLine(x , y , x , y+2*scale_y , color);
    matrix.drawLine(x+2*scale_x , y+2*scale_y , x , y+2*scale_y, color);
    matrix.drawLine(x+2*scale_x , y+2*scale_y, x+2*scale_x , y+4*scale_y,color);
    matrix.drawLine( x , y+4*scale_y , x+2*scale_x , y+4*scale_y,color);
    break; 
  case 6:
    matrix.drawLine(x ,y , x , y+(4*scale_y) , color);
    matrix.drawLine(x ,y , x+2*scale_x , y , color);
    matrix.drawLine(x+2*scale_x , y+2*scale_y , x , y+2*scale_y, color);
    matrix.drawLine(x+2*scale_x , y+2*scale_y, x+2*scale_x , y+4*scale_y,color);
    matrix.drawLine(x+2*scale_x , y+4*scale_y , x, y+(4*scale_y) , color);
    break;
  case 7:
    matrix.drawLine(x ,y , x+2*scale_x , y , color);
    matrix.drawLine( x+2*scale_x, y, x+scale_x,y+(4*scale_y), color);
    break;
  case 8:
    matrix.drawLine(x ,y , x , y+(4*scale_y) , color);
    matrix.drawLine(x , y+(4*scale_y) , x+(2*scale_x) , y+(4*scale_y), color);
    matrix.drawLine(x+(2*scale_x) , y , x+(2*scale_x) , y+(4*scale_y) , color);
    matrix.drawLine(x ,y , x+(2*scale_x) , y , color);
    matrix.drawLine(x+2*scale_x , y+2*scale_y , x , y+2*scale_y, color);
    break;
  case 9:
    matrix.drawLine(x ,y , x , y+(2*scale_y) , color);
    matrix.drawLine(x , y+(4*scale_y) , x+(2*scale_x) , y+(4*scale_y), color);
    matrix.drawLine(x+(2*scale_x) , y , x+(2*scale_x) , y+(4*scale_y) , color);
    matrix.drawLine(x ,y , x+(2*scale_x) , y , color);
    matrix.drawLine(x+2*scale_x , y+2*scale_y , x , y+2*scale_y, color);
    break;    
  }
}

void word_clock() {
  cls();

  char numbers[19][10]   = { 
    "one", "two", "three", "four","five","six","seven","eight","nine","ten",
    "eleven","twelve", "thirteen","fourteen","fifteen","sixteen","7teen","8teen","nineteen"                  };              
  char numberstens[5][7] = { 
    "ten","twenty","thirty","forty","fifty"                   };

//  byte hours_y, mins_y; //hours and mins and positions for hours and mins lines  

  byte hours = hour();
  byte mins  = minute();

  //loop to display the clock for a set duration of SHOWCLOCK
  for (int show = 0; show < SHOWCLOCK ; show++) {
    
    //check buttons  
    if(buttonA.uniquePress()){
      switch_mode();
      return;      
    }
    if(buttonB.uniquePress()){
      display_date();
      display_temp();
      word_clock();
      return;
    }
    if(buttonC.uniquePress()){
      display_off();
      return;
    }

   if ( (second() % 2) == 0) 
    { 
      PORTB |= (1<<PORTB7); // Turn built in LED on (pin 13)
    }
    else
    { 
      PORTB &= ~(1<<PORTB7); // Turn built in LED off (pin 13) 
    } 

 if (hour() == 23 && minute() == 00 && second() == 00)
    dispoff = 1;

    if (dispoff == 1)
    {
      display_off();
    }

 if (hour() == 12 && minute() == 00 && second() == 00)

    {
       gettime();
    }

    //print the time if it has changed or if we have just come into the subroutine
    if ( show == 0 || mins != minute() ) {  

      //reset these for comparison next time
      mins = minute();   
      hours = hour();

      //make hours into 12 hour format
      if (hours > 12){ 
        hours = hours - 12; 
      }
      if (hours == 0){ 
        hours = 12; 
      } 

      //split mins value up into two separate digits 
      int minsdigit = mins % 10;
      byte minsdigitten = (mins / 10) % 10;

      char str_top[8];
      char str_bot[8];
      char str_mid[8];

      //if mins <= 10 , then top line has to read "minsdigti past" and bottom line reads hours
      if (mins < 10) {     
        strcpy (str_top,numbers[minsdigit - 1]);
        strcpy (str_mid,"PAST");
        strcpy (str_bot,numbers[hours - 1]);
      }
      //if mins = 10, cant use minsdigit as above, so soecial case to print 10 past /n hour.
      if (mins == 10) {     
        strcpy (str_top,numbers[9]);
        strcpy (str_mid,"PAST");
        strcpy (str_bot,numbers[hours - 1]);
      }

      //if time is not on the hour - i.e. both mins digits are not zero, 
      //then make top line read "hours" and bottom line ready "minstens mins" e.g. "three /n twenty one"
      else if (minsdigitten != 0 && minsdigit != 0  ) {

        strcpy (str_top,numbers[hours - 1]); 

        //if mins is in the teens, use teens from the numbers array for the bottom line, e.g. "three /n fifteen"
        if (mins >= 11 && mins <= 19) {
          strcpy (str_bot, numbers[mins - 1]);
          strcpy(str_mid," ");
          //else bottom line reads "minstens mins" e.g. "three \n twenty three"
        } 
        else {     
          strcpy (str_mid, numberstens[minsdigitten - 1]);
          strcpy (str_bot, numbers[minsdigit -1]);
        }
      }
      // if mins digit is zero, don't print it. read read "hours" "minstens" e.g. "three /n twenty"
      else if (minsdigitten != 0 && minsdigit == 0  ) {
        strcpy (str_top, numbers[hours - 1]);     
        strcpy (str_bot, numberstens[minsdigitten - 1]);
        strcpy (str_mid, " " );
      }

      //if both mins are zero, i.e. it is on the hour, the top line reads "hours" and bottom line reads "o'clock"
      else if (minsdigitten == 0 && minsdigit == 0  ) {
        strcpy (str_top,numbers[hours - 1]);     
        strcpy (str_bot, "O CLOCK");
        strcpy (str_mid, " ");
      }

      //work out offset to center top line on display. 
      byte lentop = 0;
      while(str_top[lentop]) { 
        lentop++; 
      }; //get length of message
      byte offset_top;
      if(lentop<6){
        offset_top = (X_MAX - ((lentop*6)-1)) / 2; //
      }
      else{
        offset_top = (X_MAX - ((lentop - 1)*4)) / 2; //
      }

      //work out offset to center bottom line on display. 
      byte lenbot = 0;
      while(str_bot[lenbot]) { 
        lenbot++; 
      }; //get length of message
      byte offset_bot;
      if(lenbot<6){
        offset_bot = (X_MAX - ((lenbot*6)-1)) / 2; //
      }
      else{
        offset_bot = (X_MAX - ((lenbot - 1)*4)) / 2; //
      }

      byte lenmid = 0;
      while(str_mid[lenmid]) { 
        lenmid++; 
      }; //get length of message
      byte offset_mid;
      if(lenmid<6){
        offset_mid = (X_MAX - ((lenmid*6)-1)) / 2; //
      }
      else{
        offset_mid = (X_MAX - ((lenmid - 1)*4)) / 2; //
      }

      cls();
      drawString(offset_top,(lenmid>1?0:2),str_top,(lentop<6?53:51),matrix.Color333(6,0,0));
      if(lenmid>1){
        drawString(offset_mid,5,str_mid,(lenmid<6?53:51),matrix.Color333(0,6,0));
      }
      drawString(offset_bot,(lenmid>1?11:8),str_bot,(lenbot<6?53:51),matrix.Color333(6,0,0));    
      matrix.swapBuffers(false);
    }
     delay (50); 
  }
}

//show time and date and use a random jumble of letters transition each time the time changes.
void jumble() {

  char days[7][4] = {
    "SUN","MON","TUE", "WED", "THU", "FRI", "SAT"                  }; //DS1307 outputs 1-7
  char allchars[37] = {
    "ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890"                  };
  char endchar[16];
  byte counter[16];
  byte mins = minute();
  byte seq[16];

  cls();

  for (int show = 0; show < SHOWCLOCK ; show++) {

    //gettime();
    //flash led for seconds on arduino
    if ( (second() % 2) == 0) 
    { 
      PORTB |= (1<<PORTB7); // Turn built in LED off (pin 13) 
    }
    else
    { 
      PORTB &= ~(1<<PORTB7); // Turn built in LED off (pin 13) 
    }

    //check buttons  
    if(buttonA.uniquePress()){
      switch_mode();
      return;      
    }
    if(buttonB.uniquePress()){
      display_date();
      display_temp();
      jumble();
      return;
    }
    if(buttonC.uniquePress()){
      display_off();
      return;
    }

 if (hour() == 23 && minute() == 00 && second() == 00)
    dispoff = 1;

    if (dispoff == 1)
    {
      display_off();
    }

 if (hour() == 12 && minute() == 00 && second() == 00)

    {
       gettime();
    }

    if ( show == 0 || mins != minute()  ) {  

      //fill an arry with 0-15 and randomize the order so we can plot letters in a jumbled pattern rather than sequentially
      for (int i=0; i<16; i++) {
        seq[i] = i;  // fill the array in order
      }
      //randomise array of numbers 
      for (int i=0; i<(16-1); i++) {
        int r = i + (rand() % (16-i)); // Random remaining position.
        int temp = seq[i]; 
        seq[i] = seq[r]; 
        seq[r] = temp;
      }


      //reset these for comparison next time
      mins = minute();
      byte hours = hour();   
      byte dow   = weekday() - 1; // the DS1307 outputs 1 - 7. 
      byte date  = day();

      byte alldone = 0;

      //set counters to 50
      for(byte c=0; c<16 ; c++) {
        counter[c] = 3 + random (0,20);
      }

      //set final characters
      char buffer[3];
      itoa(hours,buffer,10);

      //fix - as otherwise if num has leading zero, e.g. "03" hours, itoa coverts this to chars with space "3 ". 
      if (hours < 10) {
        buffer[1] = buffer[0];
        buffer[0] = '0';
      }

      endchar[0] = buffer[0];
      endchar[1] = buffer[1];
      endchar[2] = ':';

      itoa (mins, buffer, 10);
      if (mins < 10) {
        buffer[1] = buffer[0];
        buffer[0] = '0';
      }

      endchar[3] = buffer[0];
      endchar[4] = buffer[1];

      itoa (date, buffer, 10);
      if (date < 10) {
        buffer[1] = buffer[0];
        buffer[0] = '0';
      }

      //then work out date 2 letter suffix - eg st, nd, rd, th etc
      char suffix[4][3]={
        "st", "nd", "rd", "th"                                                      };
      byte s = 3; 
      if(date == 1 || date == 21 || date == 31) {
        s = 0;
      } 
      else if (date == 2 || date == 22) {
        s = 1;
      } 
      else if (date == 3 || date == 23) {
        s = 2;
      }
      //set topline
      endchar[5] = ' ';
      endchar[6] = ' ';
      endchar[7] = ' ';

      //set bottom line
      endchar[8] = days[dow][0];
      endchar[9] = days[dow][1];
      endchar[10] = days[dow][2];
      endchar[11] = ' ';
      endchar[12] = buffer[0];
      endchar[13] = buffer[1];
      endchar[14] = suffix[s][0];
      endchar[15] = suffix[s][1];

      byte x = 0;
      byte y = 0;

      //until all counters are 0
      while (alldone < 16){

        //for each char    
        for(byte c=0; c<16 ; c++) {

          if (seq[c] < 8) { 
            x = 0;
            y = 0; 
          } 
          else {
            x = 8;
            y = 8;   
          }

          //if counter > 1 then put random char
          if (counter[ seq[c] ] > 1) {
            //ht1632_putchar( ( seq[c] -x) * 6, y, allchars[random(0,36)]); //random
            matrix.fillRect((seq[c]-x)*4,y,3,5,matrix.Color333(0,0,0));
            drawChar((seq[c] - x) *4, y, allchars[random(0,36)],51,matrix.Color444(0,5,0));
            counter[ seq[c] ]--;
            matrix.swapBuffers(true);
          }

          //if counter == 1 then put final char 
          if (counter[ seq[c] ] == 1) {
            //            ht1632_putchar( (seq[c]-x) * 6, y, endchar[seq[c]]); //final char
            matrix.fillRect((seq[c]-x)*4,y,3,5,matrix.Color444(0,0,0));
            drawChar((seq[c] - x) *4, y, endchar[seq[c]],51,matrix.Color444(3,6,0));
            counter[seq[c]] = 0;
            alldone++;
            matrix.swapBuffers(true);
          } 

          //if counter == 0 then just pause to keep update rate the same
          if (counter[seq[c]] == 0) {
            delay(4);
          }

          if(buttonA.uniquePress()){
            switch_mode();
            return;      
          }
        }
      }
    }
    delay(50);
  } 
  
}

void display_temp()

{
  matrix.setTextSize(1);
  matrix.setCursor(0,0);
  matrix.setTextColor(matrix.Color333(1, 6, 0));
  cls();
  matrix.swapBuffers(true);
  flashing_cursor(0,0,3,5,1);
  float tempC = DS3231_get_treg();
  matrix.print(tempC -2.5);
  matrix.setCursor(0,8);
  matrix.print("Deg C");
  matrix.swapBuffers(true);
  delay(3000);
  
   //check for button press and exit if there is one.
    if(buttonA.uniquePress() || buttonB.uniquePress()){
      return;
    }    
}

void display_date()
{

  uint16_t color = matrix.Color333(0,6,0);
  cls();
  matrix.swapBuffers(true);
  //read the date from the DS1307
  //it returns the month number, day number, and a number representing the day of week - 1 for Tue, 2 for Wed 3 for Thu etc.
  byte dow = weekday()-1; //rtc[3] - 1; //we  take one off the value the DS1307 generates, as our array of days is 0-6 and the DS1307 outputs  1-7.
  byte date = day();  //rtc[4];
  byte mont = month()-1; //rtc[5] - 1; 

  //array of day and month names to print on the display. Some are shortened as we only have 8 characters across to play with 
  char daynames[7][9]={
    "Sunday", "Monday","Tuesday", "Wed", "Thursday", "Friday", "Saturday"                  };
  char monthnames[12][9]={
    "January", "February", "March", "April", "May", "June", "July", "August", "Sept", "October", "November", "December"                  };

  //call the flashing cursor effect for one blink at x,y pos 0,0, height 5, width 7, repeats 1
  flashing_cursor(0,0,3,5,1);

  //print the day name
  int i = 0;
  while(daynames[dow][i])
  {
    flashing_cursor(i*4,0,3,5,0);
    drawChar(i*4,0,daynames[dow][i],51,color);
    matrix.swapBuffers(true);
    i++;

    //check for button press and exit if there is one.
    if(buttonA.uniquePress() || buttonB.uniquePress()){
      return;
    }
  }

  //pause at the end of the line with a flashing cursor if there is space to print it.
  //if there is no space left, dont print the cursor, just wait.
  if (i*4 < 32){
    flashing_cursor(i*4,0,3,5,1);  
  } 
  else {
    delay(300);
  }

  //flash the cursor on the next line  
  flashing_cursor(0,8,3,5,0);

  //print the date on the next line: First convert the date number to chars so we can print it with ht1632_putchar
  char buffer[3];
  itoa(date,buffer,10);

  //then work out date 2 letter suffix - eg st, nd, rd, th etc
  char suffix[4][3]={
    "st", "nd", "rd", "th"                    };
  byte s = 3; 
  if(date == 1 || date == 21 || date == 31) {
    s = 0;
  } 
  else if (date == 2 || date == 22) {
    s = 1;
  } 
  else if (date == 3 || date == 23) {
    s = 2;
  } 

  //print the 1st date number
  drawChar(0,8,buffer[0],51,color);
  matrix.swapBuffers(true);

  //if date is under 10 - then we only have 1 digit so set positions of sufix etc one character nearer
  byte suffixposx = 4;

  //if date over 9 then print second number and set xpos of suffix to be 1 char further away
  if (date > 9){
    suffixposx = 8;
    flashing_cursor(4,8,3,5,0); 
    drawChar(4,8,buffer[1],51,color);
    matrix.swapBuffers(true);
  }

  //print the 2 suffix characters
  flashing_cursor(suffixposx, 8,3,5,0);
  drawChar(suffixposx,8,suffix[s][0],51,color);
  matrix.swapBuffers(true);
  //delay(70);

  flashing_cursor(suffixposx+4,8,3,5,0);
  drawChar(suffixposx+4,8,suffix[s][1],51,color);
  matrix.swapBuffers(true);
  //delay(70);

  //blink cursor after 
  flashing_cursor(suffixposx + 8,8,3,5,1);  

  //replace day name with date on top line - effectively scroll the bottom line up by 8 pixels
  for(int q = 8; q>=0; q--){
    cls();
    int w =0 ;
    while(daynames[dow][w])
    {
      drawChar(w*4,q-8,daynames[dow][w],51,color);

      w++;
    }


    matrix.swapBuffers(true);
    //date first digit
    drawChar(0,q,buffer[0],51,color);
    //date second digit - this may be blank and overwritten if the date is a single number
    drawChar(4,q,buffer[1],51,color);
    //date suffix
    drawChar(suffixposx,q,suffix[s][0],51,color);
    //date suffix
    drawChar(suffixposx+4,q,suffix[s][1],51,color);
    matrix.swapBuffers(true);
    delay(50);
  }
  //flash the cursor for a second for effect
  flashing_cursor(suffixposx + 8,0,3,5,0);  

  //print the month name on the bottom row
  i = 0;
  while(monthnames[mont][i])
  {  
    flashing_cursor(i*4,8,3,5,0);
    //ht1632_putchar(i*6, 8, monthnames[month][i]); 
    drawChar(i*4,8,monthnames[mont][i],51,color);
    matrix.swapBuffers(true);
    i++; 

    //check for button press and exit if there is one.
    if(buttonA.uniquePress() || buttonB.uniquePress()){
      return;
    }
  }

  //blink the cursor at end if enough space after the month name, otherwise juts wait a while
  if (i*4 < 32){
    flashing_cursor(i*4,8,3,5,2);  
  } 
  else {
    delay(1000);
  }

  for(int q = 8; q>=-8; q--){
    cls();
    int w =0 ;
    while(monthnames[mont][w])
    {
      drawChar(w*4,q,monthnames[mont][w],51,color);

      w++;
    }


    matrix.swapBuffers(true);
    //date first digit
    drawChar(0,q-8,buffer[0],51,color);
    //date second digit - this may be blank and overwritten if the date is a single number
    drawChar(4,q-8,buffer[1],51,color);
    //date suffix
    drawChar(suffixposx,q-8,suffix[s][0],51,color);
    //date suffix
    drawChar(suffixposx+4,q-8,suffix[s][1],51,color);
    matrix.swapBuffers(true);
    delay(50);
  }


}


/*
 * flashing_cursor
 * print a flashing_cursor at xpos, ypos and flash it repeats times 
 */
void flashing_cursor(byte xpos, byte ypos, byte cursor_width, byte cursor_height, byte repeats)
{
  for (byte r = 0; r <= repeats; r++) {
    matrix.fillRect(xpos,ypos,cursor_width, cursor_height, matrix.Color333(3,0,0));
    matrix.swapBuffers(true);

    if (repeats > 0) {
      delay(400);
    } 
    else {
      delay(70);
    }

    matrix.fillRect(xpos,ypos,cursor_width, cursor_height, matrix.Color333(0,0,0));
    matrix.swapBuffers(true);

    //if cursor set to repeat, wait a while
    if (repeats > 0) {
      delay(400); 
    }
  }
}


//print menu to change the mode
void switch_mode() {
  matrix.setTextWrap(false); // Allow text to run off right edge
  matrix.setTextSize(1);
  matrix.setTextColor(matrix.Color333(2, 0, 0));
  uint16_t color = matrix.Color333(2, 0, 0);

  const char* modes[] = {"Numbers", "Pong", "Basic", "Jumble", "Word", "Random", "Set CLK"};

  byte next_clock_mode;
  byte firstrun = 1;

  mode_changed = 1; //set this flag so we don't show the date when we get out the menu, we just go straigh to the new mode.

  //loop waiting for button (timeout after X loops to return to mode X)
  for(int count=0; count< 60 ; count++) { // was 40

    //if user hits button, change the clock_mode
    if(buttonA.uniquePress() || firstrun == 1){

      count = 0;
      cls();

      if (firstrun == 0) { 
        clock_mode++; 
      } 
      if (clock_mode > MAX_CLOCK_MODE) { 
        clock_mode = 0; 
      }

      //print arrown and current clock_mode name on line one and print next clock_mode name on line two
      char str_top[9];
      char str_bot[9];

      strcpy (str_top, " ");
      strcat (str_top, modes[clock_mode]);

      next_clock_mode = clock_mode + 1;
      if (next_clock_mode > MAX_CLOCK_MODE) { 
        next_clock_mode = 0; 
      }

      strcpy (str_bot, " ");
      strcat (str_bot, modes[next_clock_mode]);

      drawString(0,0,str_top,51,color);

      drawString(0,8,str_bot,51,color);

      matrix.fillTriangle(0,0,0,4,2,2, color);

      firstrun = 0;
      matrix.swapBuffers(false);
    }
    delay(50); 
  }
  //if random clock_mode set, set next hour to change clock type 
  if (clock_mode == MAX_CLOCK_MODE - 1 ){ 
    random_mode = 1;  
    //    mode_time_up = rtc[2]; 
    mode_time_up = hour();
    clock_mode = 0;
  } 
  else {
    random_mode = 0;
  } 
}

void drawString(int x, int y, const char* c,uint8_t font_size, uint16_t color)
{
  // x & y are positions, c-> pointer to string to disp, update_s: false(write to mem), true: write to disp
  //font_size : 51(ascii value for 3), 53(5) and 56(8)
  for(char i=0; i< strlen(c); i++)
  {
    drawChar(x, y, c[i],font_size, color);
    x+=calc_font_displacement(font_size); // Width of each glyph
  }
}

int calc_font_displacement(uint8_t font_size)
{
  switch(font_size)
  {
  case 51:
    return 4;  //5x3 hence occupies 4 columns ( 3 + 1(space btw two characters))
    break;

  case 53:
    return 6;
    break;

    //case 56:
    //return 6;
    //break;

  default:
    return 6;
    break;
  }
}

void drawChar(int x, int y, char c, uint8_t font_size, uint16_t color)  // Display the data depending on the font size mentioned in the font_size variable
{

  uint8_t dots;
  if (c >= 'A' && c <= 'Z' ||
    (c >= 'a' && c <= 'z') ) {
    c &= 0x1F;   // A-Z maps to 1-26
  } 
  else if (c >= '0' && c <= '9') {
    c = (c - '0') + 27;
  } 
  else if (c == ' ') {
    c = 0; // space
  }
  else if (c == '#'){
    c=37;
  }
  else if (c=='/'){
    c=37;
  }

  switch(font_size)
  {
  case 51:  // font size 3x5  ascii value of 3: 51

      if(c==':'){
      matrix.drawPixel(x+1,y+1,color);
      matrix.drawPixel(x+1,y+3,color);
    }
    else if(c=='-'){
      matrix.drawLine(x,y+2,3,0,color);
    }
    else if(c=='.'){
      matrix.drawPixel(x+1,y+2,color);
    }
    else if(c==39 || c==44){
      matrix.drawLine(x+1,y,2,0,color);
      matrix.drawPixel(x+2,y+1,color);
    }
    else{
      for (char row=0; row< 5; row++) {
        dots = pgm_read_byte_near(&font3x5[c][row]);
        for (char col=0; col < 3; col++) {
          int x1=x;
          int y1=y;
          if (dots & (4>>col))
            matrix.drawPixel(x1+col, y1+row, color);
        }    
      }
    }
    break;

  case 53:  // font size 5x5   ascii value of 5: 53

      if(c==':'){
      matrix.drawPixel(x+2,y+1,color);
      matrix.drawPixel(x+2,y+3,color);
    }
    else if(c=='-'){
      matrix.drawLine(x+1,y+2,3,0,color);
    }
    else if(c=='.'){
      matrix.drawPixel(x+2,y+2,color);
    }
    else if(c==39 || c==44){
      matrix.drawLine(x+2,y,2,0,color);
      matrix.drawPixel(x+4,y+1,color);
    }
    else{
      for (char row=0; row< 5; row++) {
        dots = pgm_read_byte_near(&font5x5[c][row]);
        for (char col=0; col < 5; col++) {
          int x1=x;
          int y1=y;
          if (dots & (64>>col))  // For some wierd reason I have the 5x5 font in such a way that.. last two bits are zero.. 
            // dots &64(10000000) gives info regarding the first bit... 
            // dots &32(01000000) gives info regarding second bit and so on...
            matrix.drawPixel(x1+col, y1+row, color);        
        }
      }
    }          


    break;

  default:
    break;

  }
}



//set time and date routine
void set_time() {

  cls();

  //fill settings with current clock values read from clock
  //gettime();
  byte set_min   = minute();
  byte set_hr    = hour();
  byte set_dow   = weekday()-1;// rtc[3] -1; //the DS1307 outputs 1-7.
  byte set_date  = day();
  byte set_mnth  = month();
  byte set_yr    = year()-2000; //not sure about this   

  //Set function - we pass in: which 'set' message to show at top, current value, reset value, and rollover limit.


  set_min  = set_value(0, set_min, 0, 59);
  set_hr   = set_value(1, set_hr, 0, 23);
  set_dow  = set_value_dow(set_dow);
  set_date = set_value(4, set_date, 1, 31);
  set_mnth = set_value(3, set_mnth, 1, 12);
  set_yr   = set_value(2, set_yr, 10, 99);

  //write the changes to the clock chip
  //RTC.stop();

  tmElements_t t;
  t.Second = 0;
  t.Minute = set_min;
  t.Hour = set_hr;
  t.Wday = set_dow;
  t.Day = set_date;
  t.Month = set_mnth;
  t.Year = set_yr+30;

  RTC.set(makeTime(t));
  RTC.write(t);
  setTime(makeTime(t));

  //reset clock mode from 'set clock'
  cls();
  clock_mode = 0;  
}

byte set_value_dow(byte current_value){

  cls();
  char message[9] = {
    "DAY NAME"                  };
  char days[7][9] = {
    "SUNDAY  ", "MONDAY  ","TUESDAY ", "WED     ", "THURSDAY", "FRIDAY  ", "SATURDAY"                  };

  //Print "set xyz" top line
  drawString(0,0,message,51,matrix.Color333(0,1,0));

  //Print current day set
  drawString(0,8,days[current_value],51,matrix.Color333(0,1,0));
  matrix.swapBuffers(true);

  //wait for button input
  delay(300);
  while (!buttonA.uniquePress()) {
    while (buttonB.isPressed()) {

      if(current_value == 6) { 
        current_value = 0;
      } 
      else {
        current_value++;
      }
      //print the new value 
      matrix.fillRect(0,8,31,15,matrix.Color333(0,0,0));
      drawString(0,8,days[current_value],51,matrix.Color333(1,0,0));
      matrix.swapBuffers(true);
      delay(150);
    }
  }
  return current_value;
}



//used to set min, hr, date, month, year values. pass 
//message = which 'set' message to print, 
//current value = current value of property we are setting
//reset_value = what to reset value to if to rolls over. E.g. mins roll from 60 to 0, months from 12 to 1
//rollover limit = when value rolls over

byte set_value(byte message, byte current_value, byte reset_value, byte rollover_limit){

  cls();
  char messages[6][17]   = {
    "SET MINS", "SET HOUR", "SET YEAR", "SET MNTH", "SET DAY", "DAY NAME"                  };

  //Print "set xyz" top line
  byte i = 0;
  
  drawString(0,0,messages[message],51,matrix.Color333(0,2,0));  
  matrix.swapBuffers(true);

  //print digits bottom line
  char buffer[3];
  itoa(current_value,buffer,10);
  
  matrix.fillRect(0,8,31,15,matrix.Color333(0,0,0));
  drawChar(0,8,buffer[0],53,matrix.Color333(0,1,0));
  drawChar(6,8,buffer[1],53,matrix.Color333(0,1,0));
  matrix.swapBuffers(true);

  delay(300);
  //wait for button input
  while (!buttonA.uniquePress()) {

    while (buttonB.isPressed()){

      if(current_value < rollover_limit) { 
        current_value++;
      } 
      else {
        current_value = reset_value;
      }
      //print the new value
      itoa(current_value, buffer ,10);
      
      matrix.fillRect(0,8,31,15,matrix.Color333(0,0,0));
      drawChar(0,8,buffer[0],53,matrix.Color333(1,0,0));
      drawChar(6,8,buffer[1],53,matrix.Color333(1,0,0));
      matrix.swapBuffers(true);

      delay(150);
    }
  }
  return current_value;
}  
