Reaali Robootika.COM

NXT robotimaailm ja programmeerimine C-keeles

5. 9 klass 3 õppetundi: Ping-Pong mäng NXT-le

Tiigrihype_logo

See materjal on loodud Tiigrihüppe Sihtasutuse programmi ProgeTiiger raames.

Ping-pong mäng seisneb selles, et ekraanil põrkab edasi-tagasi ja paremale-vasakule pall mis võib ekraani altservast nö. maha kukkuda. Palli ekraanil hoidmiseks on allservas värav mida saab nooltega paremale vasakule liigutada ning mille pealt pall on võimeline tagasi põrkama.

 

Õppetundide jaotus

Ping-Pong mängu algoritmÕppetundide jaotuse aluseks on arvestatud ühe tunni kestuseks 2x45 min.

1.      Tund.

a.      Töötava mängu tutvustamine NXT peal

b.      Programmi algoritmi tutvustamine

c.      Värava liigutamise funktsiooni loomine.

2.      Tund

a.      Palli liigutamise funktsiooni kirjutamine

b.      Palli ja värava alamprogrammide kokkupanek

3.      Tund

a.      Raskusastme lisamine

b.      Punktiarvestuse lisamine ja initsialiseerimine

c.      Mäng valmis

 

Mängu algoritm

Vaata joonist. Programmi käivitudes küsitakse kasutajalt mängu keerukust, peale selle valmisit lähevad paralleelselt käima kaks alamprogrammi: värava liigutamine ja palli põrkamine. Need kestavad seni kuni pall kukup ekraani altservast välja. Selle peale katkestatakse mäng, kuvatakse kasutajale tema punktisumma, küsitakse uuesti mängu keerukust ja seepeale algab mäng otsast peale.

Mängu programmeerimise etapid

1.      Värava liigutamise funktsioon

2.      Palli liigutamise funktsioon (lõpmatult ekraanil põrkamas)

3.      Palli ja värava liigutamise funktsioonid kokku pandud (kas pall põrkas?)

4.      Raskusastme valimine

5.      Punktiarvestuse lisamine ja initsialiseerimine

6.      Lõplik mängu kood

Koos kommentaaride ja tühjade ridadega (koodi parema loetavuse nimel) on programmi pikkus ca 220 rida.


 

Värava liigutamise funktsioon

Antud funktsiooni eesmärgiks on liigutada väravat paremale ja vasakule. Kui nuppu hoitakse alla, liigub värav sõites ühte või teise serva.

 

int Dash;

 

//Gate protseduur joonistab ekraanile värava

//antud alamprotseduur kutsutakse välja värava liigutamise juurest

void Gate(int i){

        ClearLine(LCD_LINE8);

        TextOut(i, LCD_LINE8, "__");

}

 

//MoveTheGate funktsioon liigutab väravat paremale/vasakule

task MoveTheGate(){

while (TRUE) {

        //liigutame värava paremale kuni lõpuni

        if (ButtonPressed(BTNRIGHT, TRUE)){

        //väravat liigutatakse 5 pikseli kaupa

        //kui ollakse piksli 85 juures jääb seisma ning värav on paremal

               if (Dash < 85)

                       Dash += 5;

               else

                       Dash = 85;

               }

        //liigutame väravat vasakule kuni algusesse

        else if (ButtonPressed(BTNLEFT, TRUE)){

        //väravat liigutatakse 5 piksli kaupa vasakule

        //kui ollakse 1 piksli juures jääb seisma ning värav on vasakul

               if (Dash > 1) 

                       Dash -= 5;

               else

                       Dash = 1;

               }

        //kutsutakse välja alamprotseduur Gate mis joonistab värava asukoha

        //vastavalt oma parameetrile dash

        Gate(Dash);

        //ootame 0,1 sekundit, et värav liiguks paraja kiirusega

        Wait(100);

        }

}

 

task main(){

        StartTask(MoveTheGate);

}

 


 

Palli liigutamise funktsioon

Antud funktsioon paneb palli ekraanil põrkama ning tulemuseks on palli lõpmatu põrkumine ekraanil 45 kraadise nurga all servade suhtes.

Pall liigub igas suunas ühe pikseli ehk punkti kaupa. Ekraani kõrgus on 64 ja laius 100 pikselit ehk punkti. Ülemist serva kontrollime numbriga 56 ja paremat numbriga 93, kuna ekraanil liigub tegelikkuses „o“ täht ja NXT arvestab selle tähe asukohta vasaku-alumise nurga järgi.

 

int Ball_X;            //palli asukoht x teljel

int Ball_Y;            //palli asukoht y teljel

bool Yles;             //palli liikumise suund üles-alla

bool Paremale;         //palli liikumise suund paremale-vasakule

int Ball_Last_X;       //palli eelmine asukoht

int Kiirus = 60;       //palli liikumise kiirus, kasutatakse raskusastme määramisel hiljem

 

//funktsioon Ball joonistab ekraanile palli

void Ball(int k, int i, int j){

        ClearLine(k);

        TextOut(j, i, "o");

}

 

//MoveTheBall liigutab palli üles-alla paremal-vasakule

task MoveTheBall(){

while (TRUE)

        {

        //Ball_Last_X postisioon on vajalik, et õige ekraani rida ära puhastada

        //seega, algselt salvestatakse palli asukoht ajutisse muutujasse

        Ball_Last_X = Ball_X;

 

        //pall liigub üles

        if (Yles)

               Ball_X += 1;

        //pall liigub alla

        else   

               Ball_X -= 1;

 

               //pall liigutatakse paremale

        if (Paremale)

               Ball_Y += 1;

               //pall liigutatakse vasakule

        else

               Ball_Y -= 1;

              

 

        //kutsutakse välja palli asukohta muutmise funktsioon

        //esimene parameeter määrab rea mis kustutatakse

        //teine ja kolmas parameeter määravad palli asukoha

        Ball(Ball_Last_X, Ball_X, Ball_Y);

 

        //kontrollitakse kas pall on all või üleval

        //vastavalt olekule põrkab pall järgmise tsükli käigus        vastassuunas

        if (Ball_X > 56)

               Yles = FALSE;

        else if (Ball_X <= 2)

               Yles = TRUE;

              

        //kontrollitakse/juhitakse, kas pall peab paremale-vasakule põrkama       

        if (Ball_Y > 93)

               Paremale = FALSE;

        else if (Ball_Y < 1)

               Paremale = TRUE;

 

        //palli liikumise kiirus, edaspidi kasutatakse raskusastme jaoks

        Wait(Kiirus);

        }

}

 

task main ()

        {

        StartTask(MoveTheBall);

        }

 

 


 

Palli ja värava liigutamise funktsioonid koos

Palli ja värava liigutamise funktsioonid kokkupandult tähendavad seda, et pall saab alla jõudes aru, kas tema alla on värav või mitte. Kui palli all on värav, põrkab pall üles tagasi või muidu kukub ekraani alt läbi ja mäng on läbi.

Antud funktsioonide kokkupanekul muutub palli liigutamise alamprogramm, värava liigutamine jääb samaks, seal ei muutu midagi. Seega värava liigutamise alamprogrammi siinkohal kordama ei hakka.

Palli liigutamise alamprogrammis muutub ainult koht koodis, mis kontrollib kas pall on all, seega välja on ainult see osa toodud.

        //kontrollitakse kas pall on all või üleval

        //vastavalt olekule põrkab pall järgmise tsükli käigus        vastassuunas

        if (Ball_X > 56)

               Yles = FALSE;

 

        //kontrollitakse kas pall on all

        if (Ball_X <= 2)

               {

        //kui pall põrkab vastu alust, saad punkti

        //ja pall liigub järgmise tsükli käigus üles

        //aluse kontroll on alusest 8 pikslit paremale või 5 vasakule

        //seega värava laiuseks on tegelikkuses 13 pikselit

               if (Ball_Y >= Dash-5 && Ball_Y <= Dash + 8)

                       {

                       Yles = TRUE;

                       }

               //kui pall ei põrka vastu alust

               else

               //kui pall ei põrka vastu alust, on mäng läbi

                       {

                       PlayTone(500, MS_20);

                       StopTask(MoveTheGate);

                       ExitTo(main);

                       }

               }      

Muutub ka alamprogramm main(), kus peab välja kutsuma kaks alamprogrammi. Alamprogrammid MoveTheGate ja MoveTheBall käivad paralleelselt, kuid alljärgnevas koodis on näha, et kõigepealt käivitatakse MoveTheGate() ja seejärel väljutakse main()-st ja käivitatakse MoveTheBall(). Selline koodijärjestus on vajalik, kuna kui käivitades lihtsalt mõlemad alamprogrammid käsuga StartTask lõpeb mõlema käivitamise järel main() programm ning rakendus sulgub koheselt.

NB! Selle küsimuse võib jätta koduseks ülesandeks: Miks käivitatakse paralleelselt käivad alamprogrammid alljärgneval moel ja mitte ei käivitata mõlemaid käsuga StartTask?

task main ()

        {

        StartTask(MoveTheGate);

        ExitTo(MoveTheBall);

        }

 

Lisaks tuleb koodi algusesse kirjutada alljärgnev rida. See deklareerib kompilaatorile, et eksisteerib alamprogramm nimega main(), mida kasutatakse MoveTheBall() alamprogrammis. Kompilaator kompileerib koodi järjest ning kui kõige alguses ei oleks deklareeritud main(), tekiks kompileerimisel viga kui programmi koodis kasutatakse viidet main()-le, kuna kompilaator ei tea veel, et see alamprogramm eksisteerib.

task main();

 

 


 

Raskusastme valimine

Raskusastme valimisega muutuvad mängus 2 omadust.

1)      Esiteks muutub palli liikumise kiirus, mis teeb mängimise raskemaks.

2)      Teiseks muutub punktiarvestus: kui pall liigub kiiremini, saab iga tagasipõrgatatud palli eest rohkem punkte kui palli aeglasemalt liikumise eest ehk kergema raskusastme korral.

 

int Score;             //loeb kokku mängu jooksul saadud punktid

int Raskus = 2;        //raskusastmed on 1..3, vaikimisi on 2, st. keskmine

int Kiirus = 250;      //mängu kiirus on vaikimisi 250, kuid erinevad raskusastmed mõjutavad kiirust

 

task Start(){

string msg;

bool ExitWhile = FALSE;

 

//mängu käivitamisel kuvatakse ekraanil allolevad kirjad

ClearScreen();

msg = "-= Ping Pong =-";

TextOut(1, LCD_LINE1, msg);

msg = "----------------";

TextOut(1, LCD_LINE2, msg);

msg = "Let's play it !";

TextOut(1, LCD_LINE3, msg);

msg = "Sinu punktid: ";

msg += NumToStr(Score);

TextOut(1, LCD_LINE5, msg);

msg = "< vali raskus  >";

TextOut(1, LCD_LINE6, msg);

 

//alljärgnev tsükkel on raskusastme valimiseks ning väljutakse siis kui

//kasutaja vajutab keskmist ehk oranzi nuppu

while (!ExitWhile)

        {

        //nupuvajutus lisab raskusastmele 1-e juurde

        if (ButtonPressed(BTNRIGHT, TRUE))

               {

               while(ButtonPressed(BTNRIGHT, TRUE));

               Raskus ++;

               }

        //nupuvajutus lautab raskusastmest 1-e

        if (ButtonPressed(BTNLEFT, TRUE))

               {

               while(ButtonPressed(BTNLEFT, TRUE));

               Raskus --;

               }

       

        //järgmised if laused tagavad selle, et raskusastme valimine käiks ringiratast

        if (Raskus > 3) Raskus = 1;

        if (Raskus < 1) Raskus = 3;

        ClearLine(LCD_LINE7);

       

        //switchi abil kuvatakse ekraanil raskusastmele vastav nimetus

        //lisaks muudetakse kiiruse muutujat, mis mõjutab mängu raskust

        switch (Raskus)

               {

               case 1:

                       TextOut(1, LCD_LINE7, "  ---Kerge---");

                       Kiirus = 350;

                       break;

               case 2:

                       TextOut(1, LCD_LINE7, "  --Keskmine--");

                       Kiirus = 250;

                       break;

               case 3:

                       TextOut(1, LCD_LINE7, "  ---Raske---");

                       Kiirus = 150;

                       break;

               }

              

        //kui kasutaja vajutab keskmist nuppu, hakkab mäng pihta

        if (ButtonPressed(BTNCENTER, TRUE))

               {

               while(ButtonPressed(BTNCENTER, TRUE));

               ExitWhile = TRUE;

               }

              

        //oodatakse 100ms et välistada juhuslikud nupuvajutused

        Wait(100);

        }

       

        //programm ootab enne väljumist 3 sekundit

        //see on vajalik ainult käesoleva mooduli testimiseks

        //mängus seda ootamist tarvis pole

        Wait(SEC_3);

}

 

task main ()

        {

        StartTask(Start);

        }

 


 

Punktiarvestuse lisamine ja initsialiseerimine

Punktiarvestus toimub põhimõttel, et mitu palli suudeti tagasi põrgatada. Erinevate kiiruste korral saab iga tagasipõrgatamise eest erineva arvu punkte. See osa koodist tuleb lisada sinna, kus kontrollitakse kas pall põrkab tagasi või kukub läbi.

               if (Ball_Y >= Dash-5 && Ball_Y <= Dash + 8)
                       {
                       Yles = TRUE;
                       //siin antakse vastavalt mängu raskusele punktid
                       switch (Raskus)
                               {
                               case 1:
                                      Score ++;
                                      break;
                               case 2:
                                      Score += 5;
                                      break;
                               case 3:
                                      Score += 10;
                                      break;
                               }
                       }

Initsialiseerimine tähendab seda, et enne iga mängu alustamist saaksid pall, värav ja punktiarvestus nullitud. Selleks otstarbeks käivitatakse alljärgnev alamprotseduur.

//See protseduur algväärtustab muutujad
void Initialize()
{
        Raskus;
        Dash = 50;             //see on värav ning alguses seatakse see keskele
        Ball_X = 9;            //rida millelt pall alustab liikumist
        Ball_Y = 20;   //veerg millelt pall alustab liikumist
        Yles = TRUE;   //muutuja, mis määrab et pall liigub alguses üles
        Paremale = TRUE; //muutuja, mis määrab et pall liigub alguses paremale
        Score = 0;             //punktid nulli
        ClearScreen(); //puhastame ekraani
        Gate(Dash);            //see joonistab värava algasukoha
}

 


 

Lõplik mängu kood

Kõik eelnevalt läbi käidud programmikoodi komponendid on alljärgnevalt kokku pandud üheks töötavaks tervikuks.

Esimesel real olev task Start() on siin põhjusel, et seda kutsutakse koodis välja enne kui antud alamprogramm on deklareeritud. Seetõttu on tarvilik defineerida kompilaatori jaoks tühi alamprogramm.

task Start();
 
int Dash;
int Score;             //loeb kokku mängu jooksul saadud punktid
int Raskus=2;          //raskusastmed on 1..3, vaikimisi keskmine
int Ball_X;            //palli asukoht x teljel
int Ball_Y;            //palli asukoht y teljel
bool Yles;             //palli liikumise suund üles-alla
bool Paremale;         //palli liikumise suund paremale-vasakule
int Ball_Last_X;       //palli eelmine asukoht
int Kiirus = 60;       //palli liikumise kiirus, kasutatakse raskusastme määramisel hiljem
 
//Gate protseduur joonistab ekraanile värava
//antud alamprotseduur kutsutakse välja värava liigutamise juurest
void Gate(int i){
        ClearLine(LCD_LINE8);
        TextOut(i, LCD_LINE8, "__");
}
 
//See protseduur algväärtustab muutujad
void Initialize()
{
        Raskus;
        Dash = 50;             //see on värav ning alguses seatakse see keskele
        Ball_X = 9;            //rida millelt pall alustab liikumist
        Ball_Y = 20;   //veerg millelt pall alustab liikumist
        Yles = TRUE;   //muutuja, mis määrab et pall liigub alguses üles
        Paremale = TRUE; //muutuja, mis määrab et pall liigub alguses paremale
        Score = 0;             //punktid nulli
        ClearScreen(); //puhastame ekraani
        Gate(Dash);            //see joonistab värava algasukoha
}
 
//MoveTheGate funktsioon liigutab väravat paremale/vasakule
task MoveTheGate(){
while (TRUE) {
        //liigutame värava paremale kuni lõpuni
        if (ButtonPressed(BTNRIGHT, TRUE)){
        //väravat liigutatakse 5 pikseli kaupa
        //kui ollakse piksli 85 juures jääb seisma ning värav on paremal
               if (Dash < 85)
                       Dash += 5;
               else 
                       Dash = 85;
               }
        //liigutame väravat vasakule kuni algusesse
        else if (ButtonPressed(BTNLEFT, TRUE)){
        //väravat liigutatakse 5 piksli kaupa vasakule
        //kui ollakse 1 piksli juures jääb seisma ning värav on vasakul
               if (Dash > 1)  
                       Dash -= 5;
               else 
                       Dash = 1;
               }
        //kutsutakse välja alamprotseduur Gate mis joonistab värava asukoha
        //vastavalt oma parameetrile dash
        Gate(Dash);
        //ootame 0,1 sekundit, et värav liiguks paraja kiirusega
        Wait(100);
        }
}
 
//funktsioon Ball joonistab ekraanile palli
void Ball(int k, int i, int j){
        ClearLine(k);
        TextOut(j, i, "o");
}
 
//MoveTheBall liigutab palli üles-alla paremal-vasakule
task MoveTheBall(){
while (TRUE)
        {
        //Ball_Last_X postisioon on vajalik, et õige ekraani rida ära puhastada
        //seega, algselt salvestatakse palli asukoht ajutisse muutujasse
        Ball_Last_X = Ball_X;
 
        //pall liigub üles
        if (Yles)
               Ball_X += 1;
        //pall liigub alla
        else    
               Ball_X -= 1;
 
               //pall liigutatakse paremale
        if (Paremale) 
               Ball_Y += 1;
               //pall liigutatakse vasakule
        else 
               Ball_Y -= 1;
               
 
        //kutsutakse välja palli asukohta muutmise funktsioon
        //esimene parameeter määrab rea mis kustutatakse
        //teine ja kolmas parameeter määravad palli asukoha
        Ball(Ball_Last_X, Ball_X, Ball_Y);
 
        //kontrollitakse kas pall on all või üleval
        //vastavalt olekule põrkab pall järgmise tsükli käigus        vastassuunas
        if (Ball_X > 56) 
               Yles = FALSE;
 
        //kontrollitakse kas pall on all
        if (Ball_X <= 2)
               {
               //kui pall põrkab vastu alust, saad punkti 
               //ja pall liigub järgmise tsükli käigus üles
               //aluse kontroll on alusest 8 pikslit paremale või 5 vasakule
               //seega värava laiuseks on tegelikkuses 13 pikselit
               if (Ball_Y >= Dash-5 && Ball_Y <= Dash + 8)
                       {
                       Yles = TRUE;
                       //siin antakse vastavalt mängu raskusele punktid
                       switch (Raskus)
                               {
                               case 1:
                                      Score ++;
                                      break;
                               case 2:
                                      Score += 5;
                                      break;
                               case 3:
                                      Score += 10;
                                      break;
                               }
                       }
               //kui pall ei põrka vastu alust
               else
               //kui pall ei põrka vastu alust, on mäng läbi
                       {
                       PlayTone(500, MS_20);
                       StopTask(MoveTheGate);
                       ExitTo(Start);
                       }
               }              
               
        //kontrollitakse/juhitakse, kas pall peab paremale-vasakule põrkama        
        if (Ball_Y > 93)
               Paremale = FALSE;
        else if (Ball_Y < 1)
               Paremale = TRUE;
 
        //palli liikumise kiirus, edaspidi kasutatakse raskusastme jaoks
        Wait(Kiirus);
        }
}
 
task Start(){
string msg;
bool ExitWhile = FALSE;
 
//mängu käivitamisel kuvatakse ekraanil allolevad kirjad
ClearScreen();
msg = "-= Ping Pong =-";
TextOut(1, LCD_LINE1, msg);
msg = "----------------";
TextOut(1, LCD_LINE2, msg);
msg = "Let's play it !";
TextOut(1, LCD_LINE3, msg);
msg = "Sinu punktid: ";
msg += NumToStr(Score);
TextOut(1, LCD_LINE5, msg);
msg = "< vali raskus  >";
TextOut(1, LCD_LINE6, msg);
 
//alljärgnev tsükkel on raskusastme valimiseks ning väljutakse siis kui
//kasutaja vajutab keskmist ehk oranzi nuppu
while (!ExitWhile) 
        {
        //nupuvajutus lisab raskusastmele 1-e juurde
        if (ButtonPressed(BTNRIGHT, TRUE))
               {
               while(ButtonPressed(BTNRIGHT, TRUE));
               Raskus ++;
               }
        //nupuvajutus lautab raskusastmest 1-e
        if (ButtonPressed(BTNLEFT, TRUE))
                {
               while(ButtonPressed(BTNLEFT, TRUE));
               Raskus --;
               }
        
        //järgmised if laused tagavad selle, et raskusastme valimine käiks ringiratast
        if (Raskus > 3) Raskus = 1;
        if (Raskus < 1) Raskus = 3;
        ClearLine(LCD_LINE7);
        
        //switchi abil kuvatakse ekraanil raskusastmele vastav nimetus
        //lisaks muudetakse kiiruse muutujat, mis mõjutab mängu raskust
        switch (Raskus)
               {
               case 1:
                       TextOut(1, LCD_LINE7, "  ---Kerge---");
                       Kiirus = 120;
                       break;
               case 2:
                       TextOut(1, LCD_LINE7, "  --Keskmine--");
                       Kiirus = 60;
                       break;
               case 3:
                       TextOut(1, LCD_LINE7, "  ---Raske---");
                       Kiirus = 40;
                       break;
               }
               
        //kui kasutaja vajutab keskmist nuppu, hakkab mäng pihta
        if (ButtonPressed(BTNCENTER, TRUE))
               {
               while(ButtonPressed(BTNCENTER, TRUE));
               ExitWhile = TRUE;
               }
               
        //oodatakse 100ms et välistada juhuslikud nupuvajutused
        Wait(100);
        }
 
//initialize algväärtustab kõik mängu vajalikud omadused
Initialize();
StartTask(MoveTheGate);
ExitTo(MoveTheBall);
}
 
task main () 
        {
        Initialize();
        StartTask(Start);
        }

Add comment

Loading