MezData-Logo

1D-Pong mit Z-Eye

1D - Pong

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

 

ballOrt
Ballposition 127..0
Quellcode [zPong.ino]
// 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().