Bevezetés a programozásba 2 4. Előadás: Esettanulmány http://digitus.itk.ppke.hu/~flugi/
Pong játék Játékleírás: adott egy pálya, alul és fölül fallal, és két játékoshoz tartozó egy-egy ütő két oldalon. Az egyik játékos billentyűzettel, a másik egérrel vezérli a saját platformját. A pályán pattog egy labda, ami visszapattan a falakról és a ütőkről Akinek az oldalán kimegy a labda, veszít, és a másik játékos pontot szerez
Pong játék A játékszabály mellett fontos a program elvárt működésének váza is Címképernyő Pont számláló Tíz pontnál game over, újrakezdési lehetőséggel Az egérrel vezérlő játékos ne legyen előnyben A grafikus tartalommal most nem fogunk foglalkozni
Reprezentáció Az első tennivaló: összeszedni, mik a programban előforduló, egymástól különböző állapotok, és mi kell a megkülönböztetésükhöz Az ütők helyzete A labda helyzete A pontszámok Melyik programszakaszban vagyunk (címkép, játék, gameover)?
Típusok Lehetséges típus kombinációk labda, ütő a main() majd tartalmaz két példányt az ütő típusból, és egy labdát pálya a pálya structban számokkal a labda és az ütők helyzete labda, ütő, pálya a pálya structban egy labda és két ütő típus is mező Egyelőre ízlés szerint választunk
A munka folyamata Inkrementális programozás Mindig egy kis új kódot teszünk a programhoz Előnyök: folyamatos tesztelhetőség Hiba esetén könnyű reprodukálhatóság behatárolt okozó kódrészlet, ami miatt a hibás kód könnyen megtalálható Nem mindig a friss kód hibás, de van köze a friss hibajelenséghez
Első változat Különböztessük meg a játékállapotokat egymástól Írjunk programot, ami három állapot között váltogat Legyen mindig szóköz billentyűvel váltás
Legegyszerűbb állapotátmenet implementáció const const int int SX SX = 640; 640; const const int int SY SY = 480; 480; // //...... int int main() main() { gout.open(sx, gout.open(sx, SY); SY); int int state=1; state=1; // //...... while while (!kilep (!kilep && && gin gin >> >> ev) ev) { if if (state (state == == 1) 1) { cimkeprajzol(); cimkeprajzol(); gout gout << << refresh; refresh; if if (ev.keycode (ev.keycode == == key_space) key_space) state=2; state=2; } else else if if (state (state == == 2) 2) { // //......
A lényeg: a játéklogika végiggondolása Cél: folyamatos mozgás a labdával, a nyilakkal és az egérrel, de egyforma sebességgel Eszközök: melyik gombot nyomták le melyik gombot engedték el hová mozdult az egér időzítő A labda akkor is mozog, ha senki nem csinál semmit, tehát időzítőre szükség van
Néhány rossz megoldás Amikor a gombot megnyomják, feljebb vagy lejjebb teszem a billentyűs játékost, az egerest meg mindig olyan magasra, ahol az egér éppen van Ez súlyosan hátrányos megvalósítás a billentyűs játékossal szemben lassan mozog csapkodnia kell a gombot
Kicsit jobb megoldás Amíg a gombot nyomják, feljebb vagy lejjebb teszem a billentyűs játékost, az egerest meg mindig olyan magasra, ahol az egér éppen van Ehhez már dolgozni kell, a billentyűzet állapotát is számon kell tartani, nem elég magára a billentyűzet eseményre figyelni Még mindig gyorsabb az egeres játékos ütője
Egy igazságos megoldás Mindkét játékosnak egyforma mértékben változik az ütőjének a helyzete, a billentyűsé a billentyűzet állapotának függvényében, az egeresé pedig attól függően, hogy az egér feljebb, vagy lejjebb van-e az ütőnél Így hiába rántja meg az egeret az egyik játékos, az időzítő néhány körére lesz szükség, hogy az ütő oda is érjen
Második verzió Amikor játék állapot van, a háttér elé úgy mozgatott ütőket rajzoljunk, ahogy az előbbi gondolatmenetben elhatároztuk További változókra van szükség: billentyűzet állapota, egér helyzete mivel nem az eseményre adott reakciókban van rájuk szükség, hanem kizárólag az időzítésben
Labda mozgatás Az ütők mellett most már a labdát is lehet mozgatni Itt az ideje érvényesíteni a mozgások korlátjait, se az ütők, se a labda ne hagyja el a pályát függőlegesen Visszapattanások kezelése kézenfekvő megoldás: a sebesség nyilvántartása
Absztrakciós szintek A választott megoldás: Palya, Uto, Labda típusok A Labda és Uto magukat kirajzolni és mozgatni képes négyszögek A Palya lefordítja a technikai inputot a játékszabályok szerinti fogalmakra Megoldástér Problématér Itt az absztrakciós szint határa, a main() felől csak a Palya létezik
Láthatóság bevezetése Kiderül funkciókról, hogy rossz helyen vannak, például a labda és az ütők ütközésének vizsgálata kétféleképpen lehet elegáns: vagy a labda számol, és az ütőket kapja paraméterül, vagy az ütők számolnak, és a labdát kapják Ha valahol túl sok egy másik típus mezőinek a használata, megfontolandó, hogy tagfüggvénybe kell tenni
Tesztelés A specifikáció szerint megkülönböztethető eseteket végigpróbáljuk fekete doboz teszt A forráskód elágazásainak és ciklusfeltételeinek ismeretében azokat az eseteket próbáljuk ki, amik más-más döntési fákat eredményeznek fehér doboz teszt Automatikus tesztprogramot futtatunk code coverage, emulációk
Értékelés Mik azok a lehetséges változások, amik miatt nagyon át kell írni a kódot? Mik azok, amik miatt nem? Meddig ér a jelenlegi kód rugalmassága? Mennyire hatékony a működés? Vannak-e felesleges refresh-ek? Mennyire bonyolult kijavítani?
Lehetséges módosítások Szép grafika nem gond, az önmagukat kirajzoló objektumok mögé berakható kép Ütő relatív pozíciójának beleszámolása a labda pályájába nem gond, egy új tagfüggvény hozzáadásával megoldható, a hívás helye egyértelmű Ütő sebességének beleszámolása a labda pályájába, csavarás a sebesség skálánk csak hármas (fel, le marad). Ehhez kicsit több kell
Refactoring Alapvetés: minden programban hibás elgondolás alapján tervezett rész Refactoring: egy program átalakítása úgy, hogy közben a funkcionalitása állandó (pl átírni a láthatóság szabályozásának használatára, vagy rekordokba szervezni az adatot) Érdemes kipróbálni, ha már megírtad a beadandót: ugyanazt a feladatot oldd meg újra Az újrakezdés sokszor irreális, ilyenkor részleges refactoringot alkalmaznak
Refactoring Specifikáció teljesülésének minél kényelmesebb ellenőrzése unit test adott használati eset végigtesztelése Átalakítási lépések mindegyikénél ellenőrzés. igen időigényes lehet, szánni kell rá időt
Lehetséges fejlesztések A háttér rajzolásához csupa azonos szignatúrájú függvényt használtunk. Ezt akár egy függvénymutató típusú változóra is cserélhetnénk. Ugyanezt a szituációt kezelhetjük majd öröklődéssel is, amiről később lesz szó. A mezők jelentését felülbírálhatjuk, látható, hogy a példában a magasságot tartalmazó mezőt zömmel felezzük használatkor, tehát ha eleve a felét tároljuk, kevesebb műveletet kell végezni