/*DotMatrixPong(pong.c) untersteht der BSD-Lizenz.

Copyright (c) 2011.

Christian Hülsemeyer. Alle Rechte vorbehalten.

Weiterverbreitung in kompilierter oder nichtkompilierter Form, mit oder ohne Veränderung, 
sind unter den folgenden Bedingungen zulässig:

   1. Weiterverbreitete nichtkompilierte Exemplare müssen das obere Copyright, 
   die Liste der Bedingungen und den folgenden Verzicht im Sourcecode enthalten.
   2. Weiterverbreitete kompilierte Exemplare müssen das obere Copyright, 
   die Liste der Bedingungen und den folgenden Verzicht in der Dokumentation 
   und/oder anderen Materialien, die mit dem Exemplar verbreitet werden, enthalten.
   3. Alle Werbematerialien, die Eigenschaften oder die Benutzung erwähnen, 
   müssen die folgende Bemerkung enthalten: "Dieses Produkt enthält Software von ChiSaw entwickelt wurde."
   4. Weder der Name von ChiSaw oder die Namen der Beitragsleistenden dürfen 
   zum Kennzeichnen oder Bewerben von Produkten, die von dieser Software abgeleitet wurden, 
   ohne spezielle vorherige schriftliche Genehmigung verwendet werden.

DIESE SOFTWARE WIRD VOM VERWALTUNGSRAT UND DEN BEITRAGSLEISTENDEN OHNE JEGLICHE SPEZIELLE ODER 
IMPLIZIERTE GARANTIEN ZUR VERFÜGUNG GESTELLT, DIE UNTER ANDEREM EINSCHLIEßEN: DIE IMPLIZIERTE 
GARANTIE DER VERWENDBARKEIT DER SOFTWARE FÜR EINEN BESTIMMTEN ZWECK. AUF KEINEN FALL SIND DIE 
VERWALTUNGSRÄTE ODER DIE BEITRAGSLEISTENDEN FÜR IRGENDWELCHE DIREKTEN, INDIREKTEN, ZUFÄLLIGEN, 
SPEZIELLEN, BEISPIELHAFTEN ODER FOLGENDEN SCHÄDEN (UNTER ANDEREM VERSCHAFFEN VON ERSATZGÜTERN 
ODER -DIENSTLEISTUNGEN; EINSCHRÄNKUNG DER NUTZUNGSFÄHIGKEIT; VERLUST VON NUTZUNGSFÄHIGKEIT; 
DATEN; PROFIT ODER GESCHÄFTSUNTERBRECHUNG), WIE AUCH IMMER VERURSACHT UND UNTER WELCHER 
VERPFLICHTUNG AUCH IMMER, OB IN VERTRAG, STRIKTER VERPFLICHTUNG ODER UNERLAUBTE HANDLUNG 
(INKLUSIVE FAHRLÄSSIGKEIT) VERANTWORTLICH, AUS WELCHEM WEG SIE AUCH IMMER DURCH DIE BENUTZUNG 
DIESER SOFTWARE ENTSTANDEN SIND, SOGAR, WENN SIE AUF DIE MÖGLICHKEIT EINES SOLCHEN SCHADENS 
HINGEWIESEN WORDEN SIND. */


#ifndef F_CPU
/// @brief Die Geschwindigkeit des Mikrocontroller in Hertz, f&uuml;r _delay_ms von Bedeutung
#define F_CPU 1000000
#endif

/// @brief Dieser Pin wird als Clock-Signal f&uuml;r die seriellen Daten am 74HC595 benutzt
#define SCK PB4
/// @brief Dieser Pin wird als Clock-Signal zum Schalten der internen Register an die Output-Pins des 74HC595 benutzt
#define OCK PB3
/// @brief Dieser Pin wird zur &Uuml;bertragung der seriellen Daten zum 74HC595 benutzt
#define DS PB1

/// @brief Diese Definition steht als Funktionsparameter f&uuml;r die output_text-Funktion und bestimmt den Text als statisch
#define STATIC 0
/// @brief Diese Definition steht als Funktionsparamater f&uuml;r die output_text-Funktion und l&auml;sst den Text scrollen
#define SCROLLING 1

#include <avr/io.h>
#include <util/delay.h>
#include <avr/interrupt.h>
#include <avr/pgmspace.h>
#include <inttypes.h>

/**
 * @file pong.c
 * @brief Diese Datei enth&auml;lt das gesamte Programm/Spiel f&uuml;r den Mikrocontroller
 */

void clock_serial();
void clock_output();
void shift_high();
void shift_low();
void output(uint8_t*,uint8_t);
void matrix_output();
void matrix_screen_pixel(uint32_t);

void adc_init();
void get_adcval(uint16_t *);
uint8_t getkey();

void timer_init();

void clear_screen();
void start_game();
void game_failed();
void output_game();
void cursor_a_down();
void cursor_a_up();
void cursor_b_down();
void cursor_b_up();

void next_r_ball();
uint8_t check_ball_coll();

uint32_t rand();

void output_text(char*,uint8_t);

///@brief Diese Variable enth&auml;lt einen freundlichen Smiley zur Ausgabe auf der Punktmatrix
 uint8_t friendly_sm[5] PROGMEM = {0x0C,0x12,0x0,0x12,0x0};
///@brief Diese Variable enth&auml;lt einen freundlichen Smiley zur Ausgabe auf der Punktmatrix
 uint8_t unfriendly_sm[5] PROGMEM = {0x12,0x0C,0x0,0x12,0x0};
///@brief Diese Variable enth&auml;lt einen das Alphabet und alle Ziffern zur Ausgabe auf der Punktmatrix
 uint8_t ansi[38][5] PROGMEM = {
			    {0x12,0x12,0x1E,0x12,0x0C}, //A
			    {0x0E,0x12,0x0E,0x12,0x0E}, //B
			    {0x0C,0x02,0x02,0x02,0x0C}, //C
			    {0x0E,0x12,0x12,0x12,0x0E}, //D
			    {0x0E,0x02,0x0E,0x02,0x0E}, //E
			    {0x0E,0x02,0x0E,0x02,0x02}, //F
			    {0x1C,0x12,0x1A,0x02,0x1C}, //G
			    {0x12,0x12,0x1E,0x12,0x12}, //H
			    {0x0E,0x04,0x04,0x04,0x0E}, //I
			    {0x06,0x08,0x08,0x08,0x0E}, //J
			    {0x12,0x0A,0x06,0x0A,0x12}, //K
			    {0x0E,0x02,0x02,0x02,0x02}, //L
			    {0x12,0x12,0x12,0x1E,0x12}, //M 
			    {0x22,0x32,0x2A,0x26,0x22}, //N
			    {0x0C,0x12,0x12,0x12,0x0C}, //O
			    {0x04,0x04,0x1C,0x14,0x0C}, //P
			    {0x14,0x0A,0x12,0x12,0x0C}, //Q
			    {0x14,0x14,0x0C,0x14,0x0C}, //R
			    {0x1C,0x10,0x08,0x04,0x1C}, //S
			    {0x08,0x08,0x08,0x08,0x1C}, //T
			    {0x1C,0x14,0x14,0x14,0x14}, //U
			    {0x08,0x14,0x14,0x14,0x14}, //V
			    {0x14,0x2A,0x2A,0x22,0x22}, //W
			    {0x22,0x14,0x08,0x14,0x22}, //X
			    {0x08,0x08,0x08,0x14,0x22}, //Y
			    {0x1E,0x04,0x08,0x10,0x1E}, //Z
			    {0x0E,0x0A,0x0A,0x0A,0x0E}, //0
			    {0x1C,0x08,0x08,0x0C,0x08}, //1
			    {0x1E,0x04,0x08,0x12,0x0C}, //2
			    {0x0C,0x12,0x08,0x10,0x1E}, //3
			    {0x08,0x08,0x0E,0x0A,0x0A}, //4
			    {0x0E,0x08,0x0E,0x02,0x0E}, //5
			    {0x0E,0x0A,0x0E,0x02,0x0E}, //6
			    {0x08,0x08,0x0A,0x0A,0x0E}, //7
			    {0x0E,0x0A,0x0E,0x0A,0x0E}, //8
			    {0x0E,0x08,0x0E,0x0A,0x0E}, //9
			    {0x00,0x40,0x00,0x40,0x00}, //:
			    {0x00,0x00,0x00,0x00,0x00}}; //
			    

///@brief Diese Variable enth&auml;lt den Cursor des Spielers A. Diese Variable wird ebenfalls zur Kollisions&uuml;berpr&uuml;fung benutzt.
 uint8_t cursor_a[5] = {0x0,0x1,0x1,0x0,0x0};
///@brief Diese Variable enth&auml;lt den Cursor des Spielers B. Diese Variable wird ebenfalls zur Kollisions&uuml;berpr&uuml;fung benutzt.
 uint8_t cursor_b[5] = {0x0,0x00,0x40,0x40,0x00};
///@brief Diese Variable enth&auml;lt den Spielball. Diese Variable wird ebenfalls zur Kollisions&uuml;berpr&uuml;fung benutzt.
 uint8_t ball[5] = {0x0,0x0,0x4,0x0,0x0};

///@brief Diese Variable enth&auml;lten den Video-Buffer f&uuml;r die linke Punktmatrix. Dieser Buffer wird auf der Punktmatrix ausgegeben
 uint8_t screen_buffer_first[5];
///@brief Diese Variable enth&auml;lten den Video-Buffer f&uuml;r die rechte Punktmatrix. Dieser Buffer wird auf der Punktmatrix ausgegeben
 uint8_t screen_buffer_second[5];

///@brief Diese Variable gibt an, in welche Richtung der Ball steuert. 0 = links, 1 = rechts
volatile uint8_t ball_dir = 0;
///@brief Diese Variable enth&auml;lt eine Zeitverz&ouml;gerung f&uuml;r die Spielzyklen, damit das Spiel in angenehmer Zeit abl&auml;uft. Alle 100 Z&auml;hlungen, ein Zyklus
volatile uint16_t ball_timer = 0;
///@brief Diese Variable ist ein Zufallsz&auml;hler, welcher mit dem ball_timer subtrahiert wird und selber zu regelm&auml;&szlig;gen Zeiten inkrementiert und zur&uuml;ckgesetzt wird
 uint8_t rand_timer = 0;
///@brief Diese Variable gibt an, ob das Spiel derzeit l&auml;uft, oder nicht. 0 = l&auml;uft nicht, 1 = l&auml;uft
 uint8_t game_running = 0;
///@brief Diese Variable gibt an, ob der Ball an einem Rand das Spielfeld verlassen hat. 1 = rechter Rand, 2 = linker Rand
volatile uint8_t game_failed_var = 0x0;
///@brief Diese Variable enth&auml;lt den Spielstand f&uuml;r den Spieler A
uint8_t points_a = 0;
///@brief Diese Variable enth&auml;lt den Spielstand f&uuml;r den Spieler B
uint8_t points_b = 0;

///////////////////////////////////////////////////////////
/**
 * @brief Diese Funktion ist der Einsprungspunkt des Programmes und nimmt alle n&ouml;tigen Initialisierungen vor
 * 
 * Zun&auml;chst werden die Pins PB0 (KontrollLED), PB1 (serielle Daten), PB3 (Datenausgabe-Clock) und PB4 (serielle Daten-Clock) auf Ausgang gesetzt, 
 * damit man dessen Zust&auml;nde setzen kann. Danach wird der ADC initialisiert, welcher zum Einlesen der Tasten ben&ouml;tigt wird. Als n&auml;chster wird
 * der Timer initialisert, welcher regelm&auml;&szlig;ig einen Interrupt ausl&ouml;st, welcher sich dann um die Ausgabe und die Spielzyklen k&uuml;mmert. Mit der
 * Funktion start_game() werden da die Spieldaten initialisiert und das Spiel gestartet. In der unendlichen Schleife wird f&uuml;r eine bestimmte
 * kurze Zeitdauer abgefragt, ob eine Taste gedr&uuml;ckt wurde. Danach wird &uuml;berpr&uuml;ft, ob der Ball das Spielfeld inzwischen verlassen hat, wenn nicht
 * wird &uuml;berpr&uuml;ft, welche Taste gedr&uuml;ckt wurde. Je nach Taste wird die dazu geh&ouml;rige Funktion ausgef&uuml;hrt. Danach beginnt die Schleife wieder von
 * vorne.
 * @return Der R&uuml;ckgabewert ist 0, dieser wird jedoch nie erreicht.
 */ 

int main() {
  uint8_t key = 0;
  DDRB = (1 << PB0) | (1 << PB1)| (1 << PB3) | (1 << PB4);
  
  adc_init();
  timer_init();
  sei();
  
  clear_screen();
  output_text("PONG",SCROLLING);
  start_game();

  while(1) {
   key = getkey();
   if(game_failed_var == 0x1 || game_failed_var == 0x2) game_failed();
   switch(key) {
     case 1: cursor_a_down(); break;
     case 2: cursor_a_up(); break;
     case 3: cursor_b_down(); break;
     case 4: cursor_b_up(); break;
     default: break;
   }
  }
return 0;
}

///////////////////////////////////////////////////////////
/**
 * @brief Diese Funktion k&uuml;mmert sich um eine HIGH-LOW-Taktflanke bei dem Clockeingang f&uuml;r die seriellen Daten des 74HC595 
 * 
 * Dabei wird der daf&uuml;r benutzte Pin PB4 auf High und danach wieder auf Low gesetzt, indem das entsprechende Bit im PORTB gesetzt und wieder
 * gel&ouml;scht wird. Dadurch wird erreicht, dass alle Zust&auml;nde der internen Register einen Platz weiterr&uuml;cken (shift) und im ersten Register
 * der anliegende Zustand am seriellen Dateneingang des 74HC595 &uuml;bernommen wird.
 */ 

void clock_serial() {
  PORTB |= (1 << SCK);
  PORTB &= ~(1 << SCK);
}

///////////////////////////////////////////////////////////
/**
 * @brief Diese Funktion k&uuml;mmert sich um eine HIGH-LOW-Taktflanke bei dem Clockeingang des Output der internen Register des 74HC595
 * 
 * Dabei wird der daf&uuml;r benutzte Pin PB3 auf High und danach wieder auf Low gesetzt, indem das entsprechende Bit im PORTB gesetzt und wieder
 * gel&ouml;scht wird. Dadurch werden die Zust&auml;nde der internen Register des 74HC595 auf die Output-Pins gelegt.
 */ 

void clock_output() {
  PORTB |= (1 << OCK);
  PORTB &= ~(1 << OCK);
}

///////////////////////////////////////////////////////////
/**
 * @brief Diese Funktion schiebt einen HIGH-Zustand in den 74HC595
 * 
 * Dabei wird der serielle Datenpin PB1 auf HIGH gesetzt und ein Shift auf dem 74HC595 vollzogen.
 */ 

void shift_high() {
  PORTB |= (1 << DS);
  clock_serial();
}

///////////////////////////////////////////////////////////
/**
 * @brief Diese Funktion schiebt einen LOW-Zustand in den 74HC595
 * 
 * Dabei wird der serielle Datenpin PB1 auf LOW gesetzt und ein Shift auf dem 74HC595 vollzogen.
 */ 

void shift_low() {
  PORTB &= ~(1 << DS);
  clock_serial();
}

///////////////////////////////////////////////////////////
/**
 * @brief Diese Funktion gibt den &uuml;bergebenen 5 x 8 Bit gro&szlig;en Speicherbereich aus
 * 
 * Dabei wird der  5 x 8 Bit gro&szlig;en Speicherbereich Byte f&uuml;r Byte in den screen_buffer_second kopiert. Der Screen-Buffer enth&auml;lt die Ausgaben 
 * auf der Punktmatrix und wird mit jedem Timer-Interrupt ausgegeben.
 * @param in Dieser Parameter ist der Pointer, der auf den 5 x 8 Bit gro&szlig;en Speicherbereich zeigt
 */ 

void output(uint8_t* in, uint8_t screen) {
 uint8_t i = 0;
 switch(screen) {
   case 1: for(i = 0; i < 5; i++) screen_buffer_first[i] = in[i]; break;
   case 2: for(i = 0; i < 5; i++) screen_buffer_second[i] = in[i]; break;
   default: break;
 }
}

///////////////////////////////////////////////////////////
/**
 * @brief Diese Funktion rechnet den Ausgabe-Puffer in die reelle Ausgabe um
 * 
 * Dabei wendet diese Funktion die Multiplexing-Methode an, bei der jeweils immer nur ein Pixel auf einmal dargestellt wird, keine Konflikte
 * zwischen Zeilen und Spalten zu verursachen. Dies wird so schnell wiederholt, sodass das Endbild komplett aussieht. Dieser Vorgang wird 
 * direkt f&uuml;r beide Matrizen wiederholt. Zun&auml;chst wird f&uuml;r jede der f&uuml;nf Zeilen, eine Zeile in den row-Buffer geladen.
 * Diese Zeile wird nun auf gesetzte Bits untersucht. Wird eines an einer Stelle gefunden, wird der out-Buffer dementsprechend 
 * vorbereitet, dass dieses Bit ein darzustellenden Pixel darstellt. Wichtig ist dabei zu beachten, dass dieses gesetzte Bit, also das 
 * darzustellende Pixel auf den richtigen Pin der Shift-Register geschaltet werden muss, wodurch die komplizierten Shitfs entstehen, da
 * der out-Buffer direkt an die Shift-Register weitergetaktet wird. F&uuml;r den zweiten Bildschirm muss das Ganze zudem um 16 Bits/Pins verschoben
 * sein, da diese 16 Pins weiter angeschlossen ist. Die gesamte Zeile wird danach in matrix_screen_pixel rausgetaktet. Danach wird das n&auml;chste
 * Pixel &uuml;berpr&uuml;ft.
 */ 

void matrix_output() {
  uint8_t k,i,j,col,row = 0;
  uint32_t out = 0;
      
  for(k = 0; k < 2; k++){ //F&uuml;r beide Punktmatrix
    for(i = 0; i < 5; i++) { //Jede Spalte muss auf Punkte &uuml;berpr&uuml;ft werden
	  if(k == 0) row = screen_buffer_second[i]; //Erst die rechte Punktmatrix
	  else row = screen_buffer_first[i]; //Dann die linke Punktmatrix
	  for(j = 0; j < 7; j++) { //Dann wird jede Zeile auf ein Punkt &uuml;berpr&uuml;ft
	    if(row & 0x01) { //Wurde einer gefunden,
	      out = (1 << j); //muss die jeweilige Zeile aktiviert werden
	      out <<=5; //(Die Zeilen stehen in der Ausgabe 5 Bits weiter
	      out |= (1 << i); //und es muss die jeweilige Spalte aktiviert werden
	      out <<=1; //Welche erst ab Bit 1 anfangen
	      if(k == 1) out <<= 16; //F&uuml;r den linken Bildschirm muss das Ganze 16 Bits verschoben werden (aufgrund der Schieberegister)
	      matrix_screen_pixel(out);
	    }
	    row >>=1; // Das n&auml;chste Bit wird &uuml;berpr&uuml;ft
	  }
    }
  }
}

///////////////////////////////////////////////////////////
/**
 * @brief Diese Funktion taktet vorbereitete Zeilen mit einem gesetzten Pixel an die Shift-Register raus
 * 
 * Dabei wird jedes der gestzten Bits von hinten nach vorne &uuml;berpr&uuml;ft. Da die zu aktivierenden Zeilen Anoden und die zu aktivierenden Spalten
 * Kathoden darstellen, m&uuml;ssen die zu aktivierenden Spalten invertiert behandelt werden, sodass ein gesetztes Bit, also die Aufforderung zur
 * Aktivierung, eine Null taktet und somit einen Masse-Eingang im Shift-Register darstellt und die Spalte freigeschaltet wird. Nachdem am Ende
 * alle Bits entweder logisch Eins oder logisch Null getaktet worden sind, werden die Ausg&auml;nge mit clock_output an den Shift-Registern gleichzeitig
 * freigebenen und &uuml;bernehmen die getakteten Werte. Am Ende werden alle Steuerleitungen wieder auf Null gelegt.
 * @param input Dieser Parameter enth&auml;lt die zu taktenen Bits
 */ 

void matrix_screen_pixel(uint32_t input) {
  uint8_t i = 0;
 
    for(i = 0; i < 32; i++) {
	if((i < 26 && i > 16) || (i < 10)) {
	  if(input & 0x80000000) shift_low();
	  else shift_high();
	}
	else {
	  if(input & 0x80000000) shift_high();
	  else shift_low();
	}
	input <<= 1;
    }
    clock_output();

  PORTB &= ~(1 << DS);
  PORTB &= ~(1 << SCK);
  PORTB &= ~(1 << OCK);
  
}

///////////////////////////////////////////////////////////
/**
 * @brief Diese Funktion initialisiert den ADC (Analog to Digtial Converter) mit Werten
 * 
 * Dabei wird zun&auml;chst eine trash-Variable (M&uuml;ll-Variable, d.h diese Variable wird mit unsinnigen Daten belegt) angelegt, da der ADC am Ende der
 * Initialisierung einen Wert auslesen lassen muss, bevor dieser korrekt funktioniert. Als n&auml;chstes wird im Register ADMUX das Bit MUX0 gesetzt,
 * welches bedeutet, das Analog-Daten f&uuml;r die ADC-Konvertierungen aus dem Pin PB2 gelesen werden sollen. Im n&auml;chsten Schritt wird im Register
 * DIDR0 das Bit ADC0D gesetzt, womit angewiesen wird, dass aus dem Pin PB2 keine digitalen Daten mehr gelesen werden m&uuml;ssen. Das Register
 * ADCSRA wird mit den Bits ADPS1 und ADPS0 belegt, womit erreicht wird, dass der ADC einen Prescaler von 8 besitzt. Da dieser AVR Atmel Attiny45
 * mit einem Megahertz betrieben wird, wird mit dem Prescaler erreicht, dass die Frequenzahl im Bereich zwischen 50kHz und 200kHz liegt (Hier:
 * 125kHz). Dies wird ben&ouml;tigt, damit der ADC mit genauer Aufl&ouml;sung arbeiten kann. Das Bit ADEN wird gesetzt, damit der ADC angeschaltet wird.
 * Damit startet jedoch noch keine Konvertierung. Im letzten Schritt wird ein ADC-Wert ausgelesen, danach ist der ADC vollst&auml;ndig einsatzbereit. 
 */ 

void adc_init() {
    uint16_t trash;
    ADMUX =  (1 << MUX0); //PB2
    DIDR0 |= (1<<ADC0D); //PB2  (ADC0)
    ADCSRA = (1 << ADPS1) | (1 << ADPS0) | (1 << ADEN); //Division 8
    get_adcval(&trash); //First Conversion
}

///////////////////////////////////////////////////////////
/**
 * @brief Diese Funktion startet den ADC (Analog to Digital Converter)
 * 
 * Dabei wird das ADSC Bit im Zustandsregister ADCSRA gesetzt, womit eine ADC-Konvertierung gestartet wird.
 */ 

void adc_start() { 
    ADCSRA |= (1 << ADSC);
}

///////////////////////////////////////////////////////////
/**
 * @brief Diese Funktion liest einen ADC-Wert aus und schreibt diesen in den angegeben 16-Bit-Buffer
 * 
 * Dabei wird zun&auml;chst mit adc_start() eine ADC-Konvertierung gestartet. Danach wird solange eine Schleife durchlaufen, bis das Bit ADSC im Register
 * ADCSRA nicht mehr gesetzt ist. Ist dies der Fall, bedeutet es, dass die Konvertierung abgeschlossen ist. Danach wird der ausgelesene Wert in
 * den angegebenen 16-Bit-Buffer geschrieben.
 * @param val Dieser Parameter enth&auml;lt die Adresse des 16-Bit-Buffer, in dem der ausgelesene ADC-Wert gespeichert werden soll
 */ 

void get_adcval(uint16_t *val) {
  adc_start();
  while(ADCSRA & (1 << ADSC));
  *val = ADCW;
}

///////////////////////////////////////////////////////////
/**
 * @brief Diese Funktion wartet auf eine Tasteneingabe
 * 
 * Dabei wird der an dem Pin 2 angelegten Spannungswert &uuml;ber die get_adcval-Funktion ausgelesen. Handelt es sich um einen in Referenz zu 5V 
 * angelegten Wert von 50 oder weniger, ist die Taste 4 gedr&uuml;ckt worden. Zwischen 51 und 280 liegt die Taste 3, die Taste 2 hat einen Wertebereich
 * von 281 bis 540. Die Taste 1 ist gedr&uuml;ckt worden, wenn der Wert 790 bis 1023 betr&auml;gt. Alles &uuml;ber diesen Werte ist keine angeschlossene Taste.
 * Wurde nach zehn Auslese- und Verarbeitungsvorg&auml;ngen keine g&uuml;ltige Taste gedr&uuml;ckt oder wurde eine Taste gedr&uuml;ckt, wird die Schleife beendet und
 * die angegebene Taste zur&uuml;ckgegeben. Bei keiner g&uuml;ltigen Taste wird 0 zur&uuml;ckgegeben.
 * @return Der R&uuml;ckgabewert ist die gedr&uuml;ckte Taste
 */ 

uint8_t getkey() {
  uint8_t key = 0;
  uint16_t adc_wert = 0;
  uint16_t zaehl = 0;  
  while(1) { 
   	get_adcval(&adc_wert);
	zaehl++;
	if(adc_wert <= 50) { //Taste 4 von links
	    key = 4;
	    break;
	}
	else if(adc_wert <= 280 ) { //Taste 3 von links
	    key = 3;
	    break;
	}
	else if(adc_wert <= 540) { //Taste 2 von links
	    key = 2;
	    break;
	}// bis hier
	else if (adc_wert <= 790){ //Taste 1 von links
	    key = 1;
	    break;
	}  
	
	else if(adc_wert <= 1024) { //keine Taste
	    key = 0;   
	}
	if(zaehl >= 10) break;
 }
 rand_timer += zaehl+key;
 return key;
}

///////////////////////////////////////////////////////////
/**
 * @brief In dieser Funktion wird der Timer initialisiert
 * 
 * Dabei wird zun&auml;chst mit dem TOIE0-Bit im TIMSK-Register erreicht, dass bei einem Timer-Overflow Interrupts ausgel&ouml;st werden. Mit dem
 * CS01-Bit im TCCR0B-Register wird der Prescaler auf 8 gesetzt, sodass nur bei jedem achten Taktzyklus der Timer inkrementiert. Mit dem Wert
 * 0x0 im TCNT0-Register wird der Startwert des Timer0 gesetzt.
 */ 

void timer_init() {
  TIMSK |= (1 << TOIE0);
  TCCR0B |= (1 << CS01); //Prescaler 8
  TCNT0 = 0x0;  //Startwert 1
}

///////////////////////////////////////////////////////////
/**
 * @brief Diese Funktion bildet die anzuspringende Interrupt-Funktion bei einem Timer0-Overflow
 * 
 * W&auml;hrend dieser Funktion werden verschiedene Aufgaben erledigt. Zum einem wird die Bildschirmausgabe aktualisiert, solange das Spiel
 * am Laufen ist, zum anderen aktualisiert diese Funktion bei allen hundert Timer0-Overflows die Ballposition.
 * @param TIMER0_OVF_vect Dieser Parameter gibt den Typ des Interrupt an, bei dem diese Funktion angesprungen werden soll
 */ 

ISR(TIMER0_OVF_vect) { //Bildschirmausgabe
  ball_timer++;
  if(ball_timer % 2 && !(game_running)) return; //Bei der m&ouml;glichen Textausgabe darf nur jedes zweite Mal der Interrupt vollst&auml;ndig ablaufen
  if(game_running) output_game();
  matrix_output();
  if((ball_timer % 10) == 0) rand_timer++;
  if(ball_timer >= 100  && game_running) {
    ball_timer = 0;
    next_r_ball();
  }
}

///////////////////////////////////////////////////////////
/**
 * @brief Diese Funktion dient zur L&ouml;schung der Punktmatrix
 * 
 * Dabei wird der screen_buffer_second, der als Ausgabebereich f&uuml;r die Punktmatrix definiert ist, mit 0x0 &uuml;berschrieben.
 */ 

void clear_screen() {
  uint8_t i = 0;
  for(i = 0; i < 5; i++) {
    screen_buffer_second[i]= 0x0; 
    screen_buffer_first[i] = 0x0;
  }
}

///////////////////////////////////////////////////////////
/**
 * @brief Diese Funktion startet das Spiel und setzt alle entsprechenden Umgebungsvariablen
 * 
 * Dabei wird zun&auml;chst das m&ouml;glicherweise vorrausgegange game_failed_var wieder zur&uuml;ckgesetzt, da das Spiel wird freigegeben wird. Danach
 * werdem die Cursor wieder in ihre Ursprungsposition zur&uuml;ckgeschrieben und die Ball-Position wieder zur&uuml;ckgesetzt. Zuletzt wird die 
 * Ball-Richtung wieder nach rechts-laufend gesetzt und das Spiel mit game_running = 1 wieder freigegeben. Um optische Verz&ouml;gerungen 
 * beim Spielstart zu vermeiden wird das Startfeld direkt ausgegeben.
 */ 

void start_game() {
  uint8_t i = 0;
  game_failed_var = 0x0;
  for(i = 0; i < 5; i++) {
    cursor_a[i] = 0x0;
    cursor_b[i] = 0x0;
  }
  cursor_a[1] = 0x1;
  cursor_a[2] = 0x1;
  cursor_b[2] = 0x40;
  cursor_b[3] = 0x40;
  for(i = 0; i < 5; i++) ball[i]= 0x0;
  ball[2]= 0x4;
  ball_dir = 0;
  game_running = 1;
  output_game(); 
}

///////////////////////////////////////////////////////////
/**
 * @brief Diese Funktion gibt das Spielfeld auf den beiden Matrizen aus
 * 
 * Dabei wird zun&auml;chst mit clear_screen die Buffer der Matrizen geleert und anschlie&szlig;end mit den aktuellen Informationen der Cursor A, Cursor B.
 * und des Balls gef&uuml;llt.
 */ 

void output_game() {
  uint8_t i = 0;
  clear_screen();
  for(i = 0; i < 5; i++) screen_buffer_second[i] |= cursor_a[i] | cursor_b[i] | ball[i];
}

///////////////////////////////////////////////////////////
/**
 * @brief Diese Funktion setzt den Cursor A um einen Pixel nach unten
 * 
 * Dabei wird zun&auml;chst &uuml;berpr&uuml;ft, ob das untere Ende des Cursor A bereits erreicht ist. Falls dies der Fall ist, wird die Funktion vorzeitig 
 * verlassen. Falls nicht, wird &uuml;bernimmt jedes Byte des Cursor das Byte+1, sodass alles nach unten gesetzt wird. Das oberste Byte wird in 
 * jedem Falls leer sein und mit Nullen gef&uuml;llt. 
 */ 

void cursor_a_down() {
 uint8_t i = 0;
 if(cursor_a[0]== 0x01) return;
 else {
  for(i = 0; i < 4; i++) cursor_a[i]= cursor_a[i+1];
  cursor_a[4] = 0x0;
 }
}

///////////////////////////////////////////////////////////
/** 
 * @brief Diese Funktion setzt den Cursor A um einen Pixel nach oben
 * 
 * Dabei wird zun&auml;chst &uuml;berpr&uuml;ft, ob das obere Ende des Cursor A bereits erreicht ist. Falls dies der Fall ist, wird die Funktion vorzeitig 
 * verlassen. Falls nicht, wird &uuml;bernimmt jedes Byte des Cursor das Byte-1, sodass alles nach oben gesetzt wird. Das unterste Byte wird in 
 * jedem Falls leer sein und mit Nullen gef&uuml;llt. 
 */ 

void cursor_a_up() {
 uint8_t i = 0;
 if(cursor_a[4]== 0x01) return;
 else {
  for(i = 4; i > 0; i--) cursor_a[i]= cursor_a[i-1];
  cursor_a[0]= 0x0;
 }
}

///////////////////////////////////////////////////////////
/** 
 * @brief Diese Funktion setzt den Cursor B um einen Pixel nach unten
 * 
 * Dabei wird zun&auml;chst &uuml;berpr&uuml;ft, ob das untere Ende des Cursor B bereits erreicht ist. Falls dies der Fall ist, wird die Funktion vorzeitig 
 * verlassen. Falls nicht, wird &uuml;bernimmt jedes Byte des Cursor das Byte+1, sodass alles nach unten gesetzt wird. Das oberste Byte wird in 
 * jedem Falls leer sein und mit Nullen gef&uuml;llt. 
 */

void cursor_b_down() {
 uint8_t i = 0;
 if(cursor_b[0] == 0x40) return;
 else {
  for(i = 0; i < 4; i++) cursor_b[i]= cursor_b[i+1];
  cursor_b[4] = 0x0;
 }
}

///////////////////////////////////////////////////////////
/** 
 * @brief Diese Funktion setzt den Cursor B um einen Pixel nach oben
 * 
 * Dabei wird zun&auml;chst &uuml;berpr&uuml;ft, ob das obere Ende des Cursor B bereits erreicht ist. Falls dies der Fall ist, wird die Funktion vorzeitig 
 * verlassen. Falls nicht, wird &uuml;bernimmt jedes Byte des Cursor das Byte-1, sodass alles nach oben gesetzt wird. Das unterste Byte wird in 
 * jedem Falls leer sein und mit Nullen gef&uuml;llt. 
 */ 

void cursor_b_up() {
 uint8_t i = 0;
 if(cursor_b[4] == 0x40) return;
 else {
  for(i = 4; i > 0; i--) cursor_b[i] = cursor_b[i-1];
  cursor_b[0] = 0x0;
 }
}

///////////////////////////////////////////////////////////
/**
 * @brief Diese Funktion berechnet die n&auml;chste Position des Spielballs
 * 
 * Dabei wird zun&auml;chst &uuml;berpr&uuml;ft, ob der Spielball bereits mit einem Cursor kollidiert und dieses Ergebnis gespeichert. Sollte dies der Fall sein
 * und der R&uuml;ckgabewert ist nicht Null, wird durch Pr&uuml;fung, ob der R&uuml;ckgabewert gerade ist (rechte Seite ber&uuml;hrt) oder ungerade ist (linke Seite
 * ber&uuml;hrt) &uuml;berpr&uuml;ft, in welcher Richtung der Ball umdrehen muss. Durch die Zufallsfunktion wird entschieden ob, der Ball gerade oder in einer
 * Diagonalen zur&uuml;ckgespielt wird. Das Ergebnis wird in die neue Ballrichtung. Danach wird zur sp&auml;teren Berechnung &uuml;berpr&uuml;ft, in welcher
 * Zeile sich der Ball derzeit befindet. Bei dem switch-Block folgt nun die Berechnung f&uuml;r die Position des Balls, welche durch ball_dir bestimmt
 * ist. Das Ball-Array wird dementsprechen in die Richtung geshiftet. Sollte bei einem diagonalen Verlauf der obere oder untere Rand den Ball
 * ber&uuml;hren, wird die n&auml;chste Ball-Richtung direkt die entgegengesetzte Diagonale und der Ball der Block vorzeitig verlassen. Nachdem der Ball
 * neu berechnet wurde, wird am Ende &uuml;berpr&uuml;ft, ob der Ball die Spiel-Aus-Grenzen &uuml;berschritten hat und ein Spieler einen Punkt gewonnen hat. Sollte
 * dies der Fall sein, wird game_failed_var gesetzt und sobald die Ausf&uuml;hrung wieder in der main-Schleife ist das Spiel f&uuml;r verloren erkl&auml;ren. 
 */ 

void next_r_ball() {
  uint8_t col = 5;
  uint8_t i = 0;
  
  i = check_ball_coll();
  
  if(i != 0) {  
      if(i % 2) {  //Rechte Seite aufgeschlagen
	i = rand() % 4;
	if(i == 1) i = 0;
      }	
      else {	//Linke Seite aufgeschlagen
	i = rand() % 4;
	if(i == 0 || i == 1) i = 1;
	else i += 2;
      }
        ball_dir = i;
  }
  
  for(i = 0; i < 5; i++) if(ball[i] != 0) col = i; 

  
  switch(ball_dir) {
    
    case 0x0: //links
      ball[col] <<= 1;
      break;
    case 0x1: //rechts
      ball[col] >>= 1;
      break;
    case 0x2: //links-hoch

      if(col == 4) { ball_dir = 3; break; }
      ball[col+1] = ball[col] << 1;
      ball[col] = 0x0;
      col++;
      break;
    case 0x3: //links-runter
      if(col == 0) { ball_dir = 2; break; }
      ball[col-1] = ball[col] << 1;
      ball[col] = 0x0;
      col--;
      break;
    case 0x4: //rechts-hoch
      if(col == 4) { ball_dir = 5; break; }
      ball[col+1] = ball[col] >> 1;
      ball[col] = 0x0;
      col++;
      break;
    case 0x5: //rechts-runter
      if(col == 0) { ball_dir = 4; break; }
      ball[col-1] = ball[col] >> 1;
      ball[col] = 0x0;
      col--;
      break;
    default:
      break;
      
  }
    if(ball[col] == 0x0 && game_failed_var == 0) game_failed_var = 0x2;   //Bei A-Spieler raus
    if(ball[col] >= 0x80 && game_failed_var == 0) game_failed_var = 0x1;  //Bei B-Spieler raus

  return;
    
}


///////////////////////////////////////////////////////////
/**
 * @brief Diese Funktion &uuml;berpr&uuml;ft, ob der Ball einen Cursor ber&uuml;hrt 
 * 
 * Dabei werden zun&auml;chst beide Cursor in einem gemeinsamen Buffer gespeichert und die Zeile bestimmt, in der sich gerade der Ball befindet. 
 * Bei den vielen if-Abfragen finden &Uuml;berpr&uuml;fungen statt, ob der Ball sich auf der gleichen Position mit einem Cursor &uuml;berliegt, welcher 
 * tempor&auml;r einen Pixel in die Richtung des Balles geshiftet wird. Diese Abfrage findet auch f&uuml;r Diagonalen statt.
 * @return Der R&uuml;ckgabewert bildet die Stelle, an der der Ball kollidierte.
 */ 

uint8_t check_ball_coll() {
  uint8_t temp_coll[5] = {0x0,0x0,0x0,0x0,0x0};
  uint8_t col = 0;
  uint8_t i = 0;
  uint8_t col_point = 0;
  
  for(i = 0; i < 5; i++) { 
    temp_coll[i] |= cursor_a[i] | cursor_b[i]; 
    if(ball[i] != 0) col = i; 
  }
  
  if( (temp_coll[col] << 1 ) & ball[col] && col == 2) col_point = 1;//Rechte Seite
  if( (temp_coll[col] >> 1 ) & ball[col] && col == 2) col_point = 2;//Linke Seite
  if( (temp_coll[col] << 1 ) & ball[col] && col < 2) col_point = 3; //Rechte Seite
  if( (temp_coll[col] >> 1 ) & ball[col] && col > 2) col_point = 4; //Linke Seite
  if( (temp_coll[col] << 1 ) & ball[col] && col < 2) col_point = 5; //Rechte Seite
  if( (temp_coll[col] >> 1 ) & ball[col] && col > 2) col_point = 6; //Linke Seite
  
  return col_point;
}


///////////////////////////////////////////////////////////
/**
 * @brief Diese Funktion wird aufgerufen, falls ein Spieler den Ball durchgelassen hat
 * 
 * Dabei wird zun&auml;chst ein unfreundlicher Smiley ausgegeben. Dann wird &uuml;berpr&uuml;ft, welches Spieler einen Punkt bekommt und der Buchstabe des
 * Spielers angezeigt, welcher den Ball durchgelassen hat. Nun wird der Punktestand ausgegeben. Falls ein Spieler neun Punkte erreicht hat,
 * werden die Punkte zur&uuml;ckgesetzt und es wird ein Schriftzug mit einem Win f&uuml;r den Spieler ausgegeben, welcher gewonnen hat. Danach wird das 
 * Spiel wieder freigegeben.
 */ 

void game_failed() {
  uint8_t temp_show[5] = {0,0,0,0,0};
  uint8_t i = 0;
  
  game_running = 0;
  clear_screen();
  
  for(i = 0; i < 5; i++) temp_show[i] = pgm_read_byte(&unfriendly_sm[i]);
  output(temp_show,2);
  _delay_ms(100);
  clear_screen();
  
  switch(game_failed_var) {
    case 0x1: points_a++; break; //A hat ein Punkt gemacht
    case 0x2: points_b++; break; //B hat ein Punkt gemacht
    default: break; 
  }

  for(i = 0; i < 5; i++) temp_show[i] = pgm_read_byte(&ansi[game_failed_var % 2][i]);

  output(temp_show,2);
  _delay_ms(100);
  
  clear_screen();
  for(i = 0; i < 5; i++) temp_show[i] = pgm_read_byte(&ansi[26+points_a][i]);
  for(i = 0; i < 5; i++) temp_show[i] |= pgm_read_byte(&ansi[36][i]);
  output(temp_show,1);
  for(i = 0; i < 5; i++) temp_show[i] = pgm_read_byte(&ansi[26+points_b][i]);
  output(temp_show,2);
  
   _delay_ms(150);
   
   if(points_a == 9) {
    clear_screen();
    output_text("A WIN",SCROLLING);
    points_a = points_b = 0;
   }
   if(points_b == 9) {
    clear_screen();
    output_text("B WIN",SCROLLING);
    points_a = points_b = 0;
   }
   
   start_game();

}

///////////////////////////////////////////////////////////
/**
 * @brief Diese Funktion dient als Zufallszahlengenerator
 * 
 * Dabei wird der unter bestimmten Regeln inkrementierende rand_timer vom st&auml;ndig inkrementierenden ball_timer subtrahiert. Das Ergebnis 
 * wird zur&uuml;ckgegeben. Da rand_timer unter bestimmten Regeln und unter Einfluss der Tasten inkrementiert wird, entstehen beinahe zuf&auml;llige
 * Zahlenwerte.
 * @return Der R&uuml;ckgabewert ist die Zufallszahl 
 */ 


uint32_t rand() {
 return  (ball_timer - rand_timer);
}

///////////////////////////////////////////////////////////
/**
 * @brief Diese Funktion gibt einen Text auf den beiden Punktmatrizen aus
 * 
 * Dabei wird zun&auml;chst in der while-Schleife die Buchstaben des Textes aus der Variable text in einen text_buffer im RAM gepuffert, um
 * unn&ouml;tige, st&auml;ndige Zugriffe auf den Flash zu vermeiden. Ebenfalls wird gleichzeitig die Anzahl der Zeichen im Text gez&auml;hlt. Mit dem Parameter
 * SCROLLING l&auml;uft die Funktion weiter wie folgt ab: Da der Text gescrollt werden soll, muss f&uuml;r jeden Scroll-Schritt alles um einen
 * Pixel verschoben werden. Da die beiden Matrizen zusammen 14 Pixel auf der X-Achse besitzen und die Buchstaben am Anfang au&szlig;erhalb sind, 
 * wird um 14+(Breite_eines_Zeichens_in_Pixel*Anzahl_der_Zeichen) inkrementiert (erste for-Schleife). Nun muss jede der f&uuml;nf Zeilen f&uuml;r den 
 * temp_buff_1 und temp_buff_2 berechnet werden (zweite for-Schleife). Da Direkt jedes sichtbare Zeichen in einem Buffer f&uuml;r jede Matrix erscheinen
 * soll, wird die folgende Berechnung f&uuml;r jedes Zeichen durchgef&uuml;hrt (dritte for-Schleife). Hier wird nun erst &uuml;berpr&uuml;ft, ob das Zeichen bereits
 * minimal auf der Matrix zu sehen sein kann. Falls dies zutrifft, wird die Zeile um 5 Pixel nach rechts (Links-Shift, durch umgekehrte Reihenfolge 
 * der Ausgabe) verschoben, da das Zeichen am Rand erscheinen soll und nicht, wie im Zeichensatz angegeben in der Mitte. Danach folgt der Scrolling-Shift
 * nach links (Rechts-Shift), welcher das bereits fortgeschrittene Scrolling-Shifting einflie&szlig;en l&auml;sst und den Buchstaben an der korrekten
 * Position erscheinen l&auml;sst. Sollte j bereits die ersten sieben Pixel gescrollt sein, kann minimal nun die zweite Matrix erreicht worden sein,
 * und l&auml;sst alle dort ankommenden Pixel genauso verfahren, lediglich sieben Pixel m&uuml;ssen abgezogen werden, da die zweite Matrix eine neue
 * Matrix darstellt, ohne Kenntnis von der ersten, die bereits sieben Pixel gescrollt hat. Sind alle Zeilen berechnet wird dies in den
 * Ausgabe-Puffer der Matrizen geschrieben und danach die lokalen Puffer gel&ouml;scht zur n&auml;chsten Scrolling-Inkrementierung. 
 *
 * @param text Dieser Parameter enth&auml;lt den Pointer auf den auszugebenen Text.
 * @param func Dieser Parameter gibt an, ob der Text gescrollt und statisch erscheinen soll. (statisch funktioniert derzeit nicht) 
 */ 

void output_text(char* text,uint8_t func) { //Optimierung
  uint8_t letter = 0,i,k,j = 0;
  uint8_t temp_buff_1[5] = {0,0,0,0,0};
  uint8_t temp_buff_2[5] = {0,0,0,0,0}; //Jedes Byte bildet eine Zeile, die Bits die Spalten
  volatile uint8_t text_buffer[6][5] = {0};

  
   while(*text) { //Text vom Flash in RAM puffern
     if(*text == 32) *text = 65+37;
     for(i = 0; i < 5; i++) text_buffer[letter][i] = pgm_read_byte(&ansi[(*text)-65][i]);
     letter++;
     text++;
     if(letter > 5) break;
   }
    
  if(func == SCROLLING) {
    for(j = 0; j < (14 +(4*letter)); j++) { //Zwei Matrizen mit a 7 Spalten + die Anzahl der Pixel der Zeichen die au&szlig;erhalb des Bildschirms starten
      for(i = 0; i < 5; i++) { //Jede Zeile wird berechnet, es gibt 5 Zeilen und 7 Spalten
	for(k = 0; k < letter; k++) { //Jeder Pixel jedes Zeichens kommt in direkt zusammen in eine Zeile
	  if(j+1 > k*5) temp_buff_1[i] |= text_buffer[k][i] << 5 >> j-(k*5); //Rechts-Shift f&uuml;r 4 Pixel Zeichenabstand, Links-Shift zum scrollen
	  if(j > (k*5)+7) temp_buff_2[i] |= text_buffer[k][i] << 5 >> j-(k*5)-7; //Sobald j < 7, Ausgabe auch auf andere Matrix erweitern
	}
      }
      output(temp_buff_1,2); //Ausgabe
      output(temp_buff_2,1);
      for(i = 0; i < 5; i++) { //F&uuml;r den n&auml;chsten Scroll-Schritt, Puffer leeren
	temp_buff_1[i] = 0x0;
	temp_buff_2[i] = 0x0;
      } 
    }
  }
  
  if(func == STATIC) { //Baustelle
     for(i = 0; i < 5; i++) {
	for(k = 0; k < letter; k++) {
	  temp_buff_1[i] |= text_buffer[k][i] << 5+(4*k) >> 14;
	  temp_buff_2[i] |= text_buffer[k][i] << 5+(4*k) >> 14-7;
	}
      }
      output(temp_buff_1,2);
      output(temp_buff_2,1);    

  }
} 

