1D - Pong

Wer kennt es nicht, das gute alte Pong –Tischtennis, hier als 1D-Variante. Der Ball fliegt zwischen L15 und L0 hin und her.
Wenn Ball L15 bzw. L0 erreicht, muss er mit T2 bzw. T0 zurückgeschlagen werden. Der Treffzeitpunkt beeinflusst die Ballgeschwindigkeit. Gegen das Programm kann gespielt werden, wenn nach dem Einschalten bei blinkender L3 Taste T1 gedrückt wird, die Rolle von Spieler 2 (L15, T2) übernimmt der Computer.

// Pongspiel mit Zylonenauge V 1.0 © Oliver Mezger 28.03.2020 #include <avr/io.h> #define INIT_LEVEL_ZEIT 10 // prop. 1/Ballgeschwindigkeit unsigned char ledausgabe[]={0xe1,0xe2,0xe4,0xe8,0xd1,0xd2, 0xd4,0xd8,0xb1,0xb2,0xb4,0xb8,0x71,0x72,0x74,0x78}; volatile unsigned char vram[] = {0,0,0,0}; // LED 4er Gruppen unsigned char buttonOld=0,buttonEnter=0; void checkButton(){ unsigned char bu=~PIND>>4; // Einlesen buttonEnter = ~buttonOld & bu; buttonOld=bu; } void piep(){ // piepst bei Schlaegertreffer OCR1A = 2000; TCCR1A = 0b00100011; // Ausgang OC1B bei 0 setzen und bei OCR1B loeschen my_delay(20); TCCR1A = 0b00000011; // Sound wieder aus } void failpiep(){ // piepst bei "daneben" OCR1A = 2000; // TCCR1A = 0b00100011; // Ausgang OC1B bei 0 setzen und bei OCR1B loeschen while(OCR1A<6000){ OCR1A+=10; my_delay(2); } TCCR1A = 0b00000011; // Sound wieder aus } volatile unsigned int ticks; // wird in ISR hochgezaehlt void my_delay(unsigned int n){ ticks=0; while(ticks<n); } volatile unsigned char multiplex=0; // Anzeigenmuliplex fuer Spielstand anschalten ISR(TIMER0_COMPA_vect){ // Interrupt Service Routine jede ms @4MHz static unsigned char i = 0; static const unsigned char vramEin[] = {0b11100000, 0b11010000, 0b10110000, 0b01110000}; ticks++; if(multiplex){ PORTB = vramEin[i] | vram[i]; if (i < 3) i++; // vram durchgehen else i = 0; } } enum {AUS,FLIEG,SCHLAEGER,TREFFER,FEHLER} zustand=AUS; unsigned char levelZeit=INIT_LEVEL_ZEIT,aPunkte=0,bPunkte=0; #define ANZEIGEDELAY 1500 void anzeigenPunkte(){ int i,k=0; for(i=0;i<=aPunkte;i++){ k=k*2+1; // leuchtband } vram[0]=k&0xf; vram[1]=k>>4; k=0; for(i=0;i<=bPunkte;i++){ k=k/2+0x80; } vram[2]=k&0xf; vram[3]=k>>4; multiplex=1; my_delay(ANZEIGEDELAY); multiplex=0; } void anzeigenSieg(){ char i,k=0; for(i=0;i<4;i++){ vram[i]=0; } multiplex=1; if (aPunkte > bPunkte){ for (i=0;i<4;i++){ for(k=1;k<=15;k=k*2+1){ vram[i]=k; piep(); my_delay(50); } } }else{ for (i=3;i>=0;i--){ k=0; do{ k=k/2+8; vram[i]=k; piep(); my_delay(50); } while(k<15); } } multiplex=0; aPunkte=bPunkte=0; levelZeit=INIT_LEVEL_ZEIT; } int main(){ // Hauptprogamm DDRB=0xff; // PORTB auf Ausgang PORTD=0x7f; // PullUps an TCCR1B = 0b00011001; // Waveform Generation Mode: Fast PWM it OCR1A als Top OCR1A = 2000; // Timer 1ms OCR1B = 500; // Impulslaenge 500us TCCR0B = 3; // Systemtakt / 64 TCCR0A= 1<<WGM01; // CTC Mode mit OCR0A als TOP TIMSK = 1<<OCIE0A;// Interrupt frei geben OCR0A=62; // bei 62 Interrupt ausloesen sei(); unsigned char ballFlugZeit=10; // groesser = langsamer unsigned char up=1,ballOrt=0,autoMode=0; checkButton(); while(1){ // Automode einstellen checkButton(); if(buttonEnter&2){ autoMode=1; break; } if(buttonEnter&5) break; PORTB=0xd1; my_delay(200); PORTB=0xd0; my_delay(300); } while(1){ checkButton(); switch(zustand){ case AUS: // kein Ball in der Luft if(buttonEnter){ zustand=FLIEG; piep(); } if(up){ PORTB=0xe1; // LED0 an my_delay(5); PORTB=0xe0; // LED0 aus }else{ PORTB=0x78; // LED15 an my_delay(5); PORTB=0x70; // LED15 aus } break; case FLIEG: // Ball fliegt bis in Schlaegerbereich if(up){ if(ballOrt < 120) ballOrt++; else zustand = SCHLAEGER; // 120 } else { if(ballOrt >7) ballOrt--; else zustand = SCHLAEGER; // 7 } PORTB=ledausgabe[ballOrt>>3]; // LED=ballOrt/8 //if((ballOrt&0xf)==0)ballFlugZeit++; // Ball wird etwas langsamer break; case SCHLAEGER: // Ball ist im Schlaegerbereich if(up){ if(ballOrt < 127){ // 120..126 ballOrt++; if(autoMode && rand()%2==0) zustand = TREFFER; // ComputerGegner if(buttonEnter&0b100) zustand = TREFFER; } else{ // 127 zustand = FEHLER; aPunkte++; } } else{ if(ballOrt > 0){ // 1..7 ballOrt--; if(buttonEnter&0b001) zustand = TREFFER; } else{ // 0 zustand = FEHLER; bPunkte++; } } my_delay(ballFlugZeit/2); // Treffererleichterung break; case TREFFER: // Schlaeger trifft if(up){ // Trefferort beeinflusst Rueckfluggeschwindigkeit ballFlugZeit=ballOrt-120; // 0..6 } else{ ballFlugZeit=7-ballOrt; // 6..0 } ballFlugZeit+=levelZeit; // + LevelZeit up=!up; piep(); zustand = FLIEG; break; case FEHLER: up=!up; failpiep(); ballFlugZeit=20; zustand = AUS; if((aPunkte+bPunkte)%2==1)levelZeit--; // 1 ms schneller if(aPunkte > 6 || bPunkte > 6) anzeigenSieg(); else anzeigenPunkte(); } my_delay(ballFlugZeit); // Zeit bis zum naechsten Ort } }
Softwarebeschreibung
Variable ballOrt
Die Ballposition wird durch eine Variable ballOrt bestimmt. Damit die Spieler die Geschwindigkeit des rückgeschlagenen Balles beeinflussen können, wird der Zeitpunkt des Treffens im Schlägerbereich verwendet. Je früher getroffen wird, desto schneller fliegt der Ball zurück.
Die 16 LED-Positionen werden dazu in 8 Unterpostitionen also insgesammt128 Ballpositionen erweitert: LED-Nummer = ballOrt / 8.
Variable ballFlugZeit
Eine Variable ballFlugZeit gibt die Verzögerung in ms zwischen den Ballpositionen an. Sie wird vom Level und dem Zeitpunkt des Zurückschlagens im Schlägerbereich bestimmt. Wenn direkt beim Eintreten in den Schlägerbereich die Taste gedrückt wird werden 0 ms, sonst entsprechend der Ballposition bis 6 ms zu der ballFlugZeit addiert, der Ball fliegt langsamer.
Um die Trefferwahrscheinlichkeit zu erhöhen wird im Schlägerbereich ausserdem die ballFlugZeit um 50% erweitert.
Automatikmodus
Wenn beim Einschalten durch Drücken von Taster T1 der Automatikmodus aktiviert wird, über nimmt für Spieler 2 das Programm das Zurückschlagen des Balls. Beim Eintritt in den Schlägerbereich wird bei jeder Ballposition "gewürfelt" und mit 50% Wahrscheinlichkeit der Ball zurück geschlagen.
Aufgaben
Beschreiben Sie, wie die Spiel-Level funktionieren.
Erstellen Sie ein Zustandsdiagramm (Software) zu den Zuständen in main().