2016. július 20., szerda

Még egy kis app inventoros játék, remélem sokan bővítik

Igértem, hogy legközelebb is app inventor jön.

Célszerű az alaplépéseken túlesni, erre szerintem megfelelő az előző poszt. Megismerkedtünk már az app inventor felületével, ha minden igaz, már pattog a labdánk szépen. Lehet hogy valaki már azt is megcsinálta (sőt, biztos, még a poszt írása előtt), hogy belül egy mozgó dobozon is pattoghatnak a golyók, vagy változtatják a színüket, ha hosszan nyomjuk meg valamelyik gombot (longclikk esemény). Jó kis játék, de szeretnénk eme egyszerű eszközökkel csinálni valami látványosabbat. Az egyik diák (Sohár Attila) mutatott egy játékot a telefonján, egyből megtetszett, ezt egyszerű megcsinálni, és annó a mikrogépeken számtalan hasonló egyszerű kis játék futott. Sokkal könnyebb feladat, mint a tévéfoci megcsinálása, bár az sem olyan nagy ördöngösség. Azt a projektet azért ignorálom egyébként, mert annál kiütközött ennek az eszköznek egy komoly hiányossága, nem támogatja a multitouch-ot, mert a cél android rendszer túl régi, az még nem tudta ezt. Ezért is idegesít az, hogy bezzeg a telefonhívást belepakolták. Mindegy, ajándék lónak ne nézzük a fogát.

Tűzzük ki a célt.

Egy repülővel - én egy barna téglalappal helyettesítem - repülünk egyre lejjebb, és az alattunk levő oszlopokat próbáljuk eltüntetni, hogy nehogy nekik menjünk. Hajjaj, ez bonyolultnak tűnik.
Nem az.
Ha még emlékszünk a sprite-okra, akkor tudjuk, hogy figyelni tudják az egymásnak ütközésüket, van sebességük, irányuk, jobban belegondolva csak össze kell dobálni az elemeket, és készen is vagyunk. A legószerű programalkotás nagyon aranyos, de itt már lassan előjön a leírt programkód előnye. Nem is fogom lépésenként "lefotózni" most, én sem szeretek feleslegesen kattintgatni. (A jövő egyébként szerintem valami vegyes megoldás lesz, 3d-ben úszkáló objektumokkal...)

A design rész elkészítésére nem pazarlok helyet, azt megtettem már az előzőekben. Itt már használunk képi elemeket, azt az általunk kedvelt képszerkesztővel - windows alatt paint, én linux alatt pinta-t használtam, de ott van az ingyenesen elérhető, komoly tudású Gimp is mindkét rendszeren - készíthetjük el.



A bombánk alapesetben nem látható, csak a kép miatt tettem azzá. Gyanús, hogy nem a legutolsó változattal dolgozom most, ha kiderül, akkor majd cserélem az exportált fájlt.
Ugyanúgy mint a múltkor, a képernyőnkre dobozokat helyezhetünk el, amelyekbe belül vagy újabb dobozokat rakhatunk, vagy ha az illető dobozobjektumunknak más a "feladata", akkor "befelé" azt tesz, ami akar. Illetve amit az objektum készítője akart. Szövegdobozt még nem raktunk ki, pedig a legeslegelső appnak elvileg csak annyit kellett volna tartalmaznia, hogy egy szövegdobozba feliratot írunk egy gomb lenyomásakor, a hosszú lenyomáskor meg egy másik feliratot, pl azt, hogy hú de erős vagy ;). Szerintem ezt mindenki meg tudja már csinálni, ha nem, akkor majd leírom azt is nulladik lépésként egyszer. A szövegdoboz belsejébe nem más dobozokat, hanem szöveget rakhatunk.
Nekünk egy canvasra van szükségünk most, meg két gombra. Az objektumok elnevezése itt válik fontossá, már elég sokan vannak :D.
A nem látható objektumokat is tekinthetjük dobozoknak :D. Rájuk később térek ki.
Célszerű megnézni az objektumok tulajdonságait (property-k, adattagok), például a gombok lehetnek lekerekítettek is akár (shape/oval), tehetünk hátteret, változtathatunk a színeken, satöbbi.

A repülő

A repülőnk fenn kezd, az iránya 0 - emlékszünk, az a vízszintes irány -, be kell állítanunk a sebességét, innen csak annyi a dolgunk, hogy a szélnek ütközésnél vagy lejjebb megyünk, vagy ha a képernyő alján vagyunk, nyert a játékos. Ha egy másik objektumnak ütköztünk, akkor meghaltunk. Itt az ügyesek csinálhatnak robbanás effektet. (Egy sprite amit láthatóvá teszünk, esetleg egy hangfile lejátszása.)

A "bomba" 

Ha megnyomjuk a bombagomb-ot, akkor amennyiben nem látható - magyarul nem zuhan éppen -, akkor a repülő alá álltjuk be az x,y koordinátáit, az iránya 270 fok (lefelé), aztán csak sebességet kell beállítanunk neki. Onnan két esemény történhet vele. Vagy a képernyő aljának ütközik, vagy valamelyik oszlopnak. Ha a képernyő aljára ér (edgereached esemény), akkor láthatatlanná tesszük és kész, jöhet az újabb gombnyomás. Ha egy oszlopnak ütközik, akkor az ütközés eseménynél rendelkezésünkre áll a másik objektum neve. Így nem kell minden egyes oszlopra ugyanazt a parancssorozatot beírnunk. (Ez egy aránylag új lehetőség az app inventorban.)
Az oszlop magasságát csökkentjük. Ennyivel lejjebb is kell raknunk, mert a koordinátáink felülről lefelé "nőnek". Ha csökkentjük a magasságot, alulról fog hiányozni a megfelelő darab.
Sajna most sem tudom kihagyni a tanulságot :). Bármilyen alkalmazás készítésénél az egyik legfontosabb dolog a tesztelés. Minden eddigi kornak megvolt az aktuális megváltó csodaszere, ilyen volt annak idején a struktúrált programozási módszerek elterjedése, ide tartozik az OOP is természetesen, manapság a TDD (test driven development), vagyis a tesztelés központú fejlesztés az aktuális megmentő. Nem véletlenül alakulnak ezek ki, ne feledjük.
Az előbb rátekintettem a blokk nézetre, és a gyanúm beigazolódott. Nem a végleges változat van itt nálam, és nem is emlékszem hogy melyik diáknál van a legjobb, letesztelt példány. No de sebaj. Így marad, legalább kapok visszajelzést hogy hol van bug :). (Ha nem, akkor majd tesztelek, javítok magam, de jobban örülnék a visszajelzésnek.)

Térjünk át a blokkokhoz.

Az imént említettem a tesztelést. Sajna hiába pakoltuk a képernyőnk aljára az oszlopainkat, bizonyos telefonokon a levegőben lógtak. Úgyhogy meg kellett oldanunk ezt a problémát. A képernyőnkhöz is tartoznak események. Amire nekünk szükségünk van most, az az inicializálás.




A bombánk láthatóságát megszüntetjük, beállítjuk az oszlopok magasságát, és a canvas magasságát figyelembe véve mindet elhelyezzük alulra. A repülőnket a bal felső sarokba, az irányát jobbra, és kész is vagyunk. Ha valaki ügyes, megoldhatja, hogy minden ütközésnél megforduljon. (Hint: a bounce metódus pont ezt csinálja, csak az y koordinátát szintén lejjebb kell tenni.)

A startgomb lenyomásával indul a játék

Azért hogy elférjen a képen, kivettem a blokk aljáról elemeket, és mellétettem balra, természetesen vissza is helyeztem utána a helyére.





Ugyanazt megtesszük, amit az initnél is - igen, lehetne rá eljárást is írni és meghívni, így nem lenne duplikálás, javításnál nem kell két helyre figyelni, ha ez valakinek eszébe jutott, gratula ;) -, Mindegyik torony láthatóságát beállítjuk, ne feledjük el, hogy lehet hogy mindegyiket eltüntettük már...
A végén "sebességbe" tesszük a repülőnket, és indulhatunk is. Itt még illene a bombagombot is engedélyezni, illetve az indítás előtt letiltani, de mint említettem, szeretjük a hibákat. Most még. Hogy majd ne kövessük el őket élesben :D. No meg a start lenyomása előtt bombázhatunk így :D. Hiába persze, mert a torony visszaemelkedik aljas módon ;).

Három apró részlet.

Itt még az előbb az repülő.edgereached-nél nem volt ott az if. Azt hittem, hogy lementettem azt a képernyőképet, de úgy fest az kimaradt. Itt az volt a hiba, hogy alulra érve nem történt semmi. Az else ágban most csak annyi található, hogy megáll a repülőnk. Itt lehet új képernyőt nyitni, pontszámot növelni, ízlés szerint.
Itt lehetne megtenni azt amit fent említettem. Ha az x koordináta 5-re állítása helyett a repülő bounce metódusát hívnánk meg az edge-t paraméterként megadva, akkor visszafordulna..Érdemes kipróbálni. Akár be is lehet kérni, hogy így, vagy úgy viselkedjen. Nem bonyolult :D.

Ha a repülönk nekimegy valaminek, akkor most nemes egyszerűséggel bezárjuk az applikációnkat.

Ha lenyomjuk a  bomba gombot, akkor amennyiben nem látható, illetve nincs engedélyezve - tehát nem zuhan épp -, akkor berakjuk a repülő alá, beállítjuk az irányát - mit is jelent egy imagesprite-nál a rotate tulajdonság :D? -, sebességét, engedélyezzük, láthatóvá tesszük, és már mehet is :D.

Ha a bombánk leér (edgereached esemény), akkor megszüntetjük a láthatóságát, és nem engedélyezzük.

Mindjárt készen is vagyunk :).


Nem tudjuk, hogy melyik toronynak ütköztünk, de nem is érdekes, megkapjuk paraméterként, és kihasználva az új lehetőséget

hogy megjelent az anycomponent elem a programban, mivel bármelyik oszloppal ugyanazt kell tennünk, megtesszük azt.



A legalsó néhány elemet most is elmozgattam a helyéről, hogy elférjen a képen.
Először elimináljuk a bombát.
Ha az adott oszlop magassága nagyobb mint 20, akkor csökkentjük a magasságát, majd ugyanennyivel lejjebb toljuk.
Ha kisebb mint 20, akkor a hosszát nullára állítjuk, a láthatóságát megszüntetjük, ugyanúgy ez engedélyezettségét is. (Különben ütközne vele a repülőnk, hiába nem látszik.)

Kész is vagyunk :D.

Az importálható program itt található.
Ha nem működik, sikítsatok :D.

2016. július 18., hétfő

Egy kis android, oop bevezető.

Egy kis múltba révedés....

Figyelem a mai kor gyermekeit, akik már akkor tudták kezelni a számítógépet, az egeret, amikor még beszélni sem tudtak - a saját fiam rá az egyik példa, képes volt valami kínai írásokkal teletúzdelt flash-es játéknál is megtalálni a play gombot, amivel én nem tudtam megküzdeni ;-) -, de nem kapták meg azt az örömöt (majdnem élm...t írtam, de attól a szótól sikítófrászt tudok kapni, mert nagyon rátaláltak a marketingesek), amit mi.

Az első számítógép amivel találkoztam a TRS-80 számítógép klónja volt. A HT-1080Z -t kipróbálhatja most is bárki, hiszen van hozzá emulátor. Ma csak mosolyogni lehet a "képességein", 1.7 mhz-es processzor, ami a tetejében nem egy órajelciklus (sőt, kevesebb) alatt dolgozott fel egy utasítást, mint a maiak, 16 kilobyte ram, 8 kilobyte-nyi "operációs rendszer", amit mondjuk azért ugyanúgy a Microsoft készített. Nekem Bill Gatesről nem csak az az élményem, hogy állandóan lefagy a cucca, hanem a zseniális akkumulátor törlő XOR AX utasítás, ami egy bájtból, két órajelciklus alatt elintézte a dolgát, a tetejében a flageket is beállította. Nyamm, még ma is bele tudok borzongani :D.

Nem volt informatikatanár, a tetejében egyetlen egy gépe volt akkor az iskolának amikor szerencsésen odaérkeztem. Volt viszont néhány lelkes diák, meg egy szintén lelkes fizika tanár, akik egymást segítve fedezték fel a masinát. Hihetetlen, hogy mennyi mindent meg tudtunk tanulni, gyakorlatilag a semmiből. Ezt az egymásnak segítő, egymást inspiráló közeget kerestem azóta is mindenütt, illetve annak örülnék, ha hasonlót elő tudnék segíteni a mai közegben is.

Emiatt örültem meg az MIT app inventorának.
Egyszerű. Azonnali eredményt ad, nem kell túl bonyolult dolgokkal szembesülni, noha a mélyben azért ott van sok minden.

Miről is lesz most szó?

El fogunk készíteni egy androidon futó egyszerű alkalmazást, és közben megtudunk néhány dolgot az OOP alapú programokról, amit majd később hasznosítani tudunk. Félreértés ne essék, igazából nem programot írunk, csak megtanulunk objektumokat használni. Az igazi erőssége ennek a rendszernek éppen a korlátozott mivoltából fakad. Néhány - fontos - dolgot itt nem tudunk megcsinálni, de éppen ez segít minket abban, hogy használni tudjuk az objektumokat, és a hiányosságok segíthetnek minket a megismerésben.
Sajnos csak androidon tudnak futni az így elkészült alkalmazások, a tetejében grafikus felületen kell dolgoznunk, ez a nem látó kollégák számára nem szerencsés, és tudtommal nincs még meg az a program, amivel ők is könnyedén birtokba vehetnék ezt az eszközt. (Mindenesetre már tárgyaltunk róla, hogy mi lenne a jó megoldás ez ügyben, de korlátozottak az erőforrásaink. Egyszer talán majd eljutunk oda is, vagy megtaláljuk a vágyott segédprogramot, illetve hardware kiegészítőt, ki tudja?)

Első lépések az app inventor 2-ben.

 Szükségünk lesz egy gmail fiókra. Ez már a második megkötés, tudom. Android, meg gmail fiók. Ez van.
 Csépeljük be a google keresőjébe, hogy app inventor 2.
 ai2.appinventor.mit.edu

 Be kell jelentkeznünk a google azonosítónkkal. A felhasználási feltételeket én már végigolvastam, és elfogadtam pedig rigorózus vagyok ilyen ügyekben, de attól még nem árt átböngészni.


Üdvözöl bennünket, túrát kínál fel, én ezt ignorálni szoktam jellemzően, bár biztosan nem árthat meg.
Nyissunk egy új projektet (projekts/new projekt), én most a blogbalabdás nevet adtam neki. Amit javítottam is rögvest blogbalabdas-ra, mert nem tudott mit kezdeni az ékezettel a projekt nevében. (Most nem térnék ki az ASCII kódtábla és a magyar nyelv küzdelmes kapcsolatára, megszenvedtem én is az elmúlt 30 akárhány év alatt, de a mai népek sincsenek ebből a buliból kihagyva ;).)
Hümm. Változik ez a platform is, jöttek új elemek. Azért a social/phonecall -nak nem nagyon örülök. Szerintem ez veszedelmes dolog, kéretik nem szórakozni vele. Ehh. Persze a futtató környezet miatt követeli magának az app a telefonhívás kezdeményezést, amit 6-os droid alatt meg sem tudunk tagadni tőle, morc lettem emiatt. (Kéretik bekapcsolni a repülőgép üzemmódot a haverok appjainak futtatásakor!)

Bal oldalon (palette) látjuk az objektum osztályokat (user interface,layout,media,drawing and animation,sensors, social,storage....), azokon belül pedig a használható objektumainkat.
Középen balra (viewer) a form (screen) objektumainkat, jobbra (components) a felhasznált komponenseinket, illetve jobb oldalon az éppen kijelölt objektum adattagjait (properties).

Meg szoktam említeni, hogy az OOP egyik komoly előnye mutatkozik meg az arcunk előtt éppen. A böngészőnk által használt nyelv - javascript - csak hasonlít az OOP-re, illetve annak egy kiterjesztéseként is felfogható, de semmiképpen nem azonos az androidon futó java-val. Mégis itt készítünk el valamit, ami majd "ott" fut. Ezt bővebben majd később fejtjük ki, mindenesetre már itt is látszik annak az ereje, hogy a programjaink, illetve objektumaink "belseje" el van rejtve a külvilág elől, azokból csak a publikus adattagok, illetve metódusok az érdekesek. Van az app inventorhoz böngészőben futtatható emulátor is, pont ennek köszönhetően.

Szemfülesek észrevehetik a blocks gombot ott fenn, de azzal majd később foglalkozunk.

Első csapásmérésként írjuk át a screen1 nevű képernyőnk szövegét - nem a nevét, bár azt is megtehetnénk, de most az nem annyira lényeges - Pattogólabdára. Jobb oldalt a properties résznél alul van a title adattag, ott tehetjük ezt meg. Már tettünk is valamit :D.

No de tűzzük ki a feladatot.

Ki fogunk rakni a képernyőre két gombot (illetve dupla gombokat) úgy, hogy az egyik a bal oldalon legyen, a másik a jobb oldalon, valamint egy labda fog pattogni egy dobozban, aminek a gombok segítségével tudjuk befolyásolni a sebességét, illetve az irányát.
Amit csak látunk magunk előtt, minden objektum. (Nem megyünk bele nagyon a részletekbe, majd szépen kialakul nemsokára, hogy mi is az. Legalábbis remélem.) A képernyő maga is az. Helyezzünk el benne két gombot. bal oldalon a palette osztályból a button (gomb) objektumot húzzuk rá a screen1-re. Kétszer.


Hümm. Ezek egymás alá helyezkedtek el. A screen objektumba helyezett egyéb objektumok nem tudják meghatározni a saját helyüket. Megnézhetjük, a gomb objektumot kiválasztva, jobb oldalt nem találunk olyan property-t ami erre akár csak utalna. Akkor hogyan csináljuk meg a feladatunkat? Bizony, amikor ezeket az objektumosztályokat tervezték, nem akartak teljesen szabad kezet adni az objektum használóinak. (Egyszerűbb így a változásokat, pl. a képernyőforgatást kezelni, mindennek oka van.)
Jó. Nem akartak. De itt nem tudunk belepiszkálni az objektumokba, csak használni tudjuk őket, most mi legyen?
A screen objektum speciális "dobozkákat" tud elhelyezni magában, a tetejében felülről lefelé teszi ezt. No de lehet hogy van olyan doboz, ami magán belül más szabályokat követ.
Voila!
Tényleg van ilyen.
Nézzük csak meg a layout objektumcsoportot. Ott van ilyen, hogy hozizontalarrangement. Az kell nekünk. Tegyük is rá a screen-re, és húzzuk bele a két gombot. (A kék jel segít "beletalálni", nem is olyan egyszerű.)



Jó, de hogy lesz a két gomb a két szélén? Fogós, ravasz kérdés, de nem véletlenül lett ez a feladat. Továbbra sem tudunk megadni fix pozíciót. A components oszlopban kattintsunk a horizontalarrangement1 komponensünkre, és nézzük meg az állítható értékeket (properties). Szélesség (width), magasság (height), mind a kettő automatikus. Legyen a width fill parent. Hopp. A konténerünk kiment a képernyő széléig. Ok, de a gomb nem. Mi van akkor, ha közéteszünk még egy gombot, aminek a szélessége szintén fill parent, a text adattagja pedig üres?


A középső gomb "széttolta" a másik kettőt. Hurrá. (Az automatic hatására az objektum belülről, a benne levő elemekből számolja ki a szélességét, a fill parent esetén pedig megpróbálja kitölteni azt az objektumot, amiben van.)
Na most akkor töröljük is ki a három gombunkat.
components, gomb, delete.
Kell egy jobbra, balra és egy fel,le gombpár. A fel, le egymás alatt értelem szerűen. Az eddigiek alapján akkor a fel/le gombokat egy layout/verticalarrangement dobozba kell raknunk. Ha valaki ügyes, akkor nem feliratokat ír a gombokra, mert azt csak a magyarok értik meg, hanem a gombok képeit változtatja meg a megfelelően megrajzolt nyilakkal. Ezt most én kihagyom, a diákok jellemzően roppant gyorsan megtalálják a gomboknál az images adattagot, és némi paint bűvészkedés után már meg is vannak vele. Minden gombnál a text propertyt állítsuk be.
Nevezzük át a gombjainkat a funkció szerint. A components sorban ráállunk, és a rename gombbal tehetjük ezt meg.
Valami ilyesmit kell kapjunk. Természetesen nem árt megnézni a különböző komponensek állítható értékeit, nem csak azokat, amelyekre én felhívom a figyelmet.



Jól van, ott vannak a gombok, de hol a labda?

A képernyő csak dobozokkal tud valamit kezdeni, valóban. Hol lesz nekünk labdánk? Nagyon régi megoldás a sprite (szellem) technika én a commodore64 típusú gépen futottam vele össze először, de gyanítom hogy már akkor sem volt új dolog. Játékok készítésénél nagyon hasznos, amikor valamilyen grafikai objektumot úgy tudunk mozgatni a képernyőn, hogy nem kell foglalkoznunk szinte csak azzal, hogy hol van, és milyen sebessége, iránya van, minden mást intéz helyettünk maga a sprite. Ha két sprite ütközik pl., akkor arról majd ők tájékoztatnak. (A C64-nél ezt külön hardware, egy segédprocesszor intézte!)

Mindjárt lesz labdánk is, de előbb egy olyan dobozt kell elhelyeznünk, amibe belepakolhatjuk. Nyilván a drawing and animation objektumcsoportnál kell keresgélnünk. Ő a canvas (kanavász, vászon).
A szélessége és a magassága is legyen fill parent. Imígyen:


Persze állíthatjuk a háttérszínét például, de háttérképet is adhatunk neki.
Tegyünk rá egy labdát. (ball)
Az interval értékét állítsuk át 100-ról 10-re. Ennyi millisecundumonként történik a labdánkkal valami. Ha 100-on maradna, akkor csak tizedmásodpercenként mozdulna meg, amit már szaggatottnak érzékel a szemünk, azt hihetjük hogy az eszközünk lassú, pedig ez nem így van. Ha kicsinek találjuk a labdánkat, akkor a sugarát (radius) állítsuk nagyobbra. A headinget mondjuk 12-re. (Ez az irányát jelenti, jobbra 0, felfelé 90, balra 180, lefelé 270, adja magát, ha az ember tudja hogy egy kör 360 fokos :)...)
A sebesség legyen mondjuk 2 egyelőre.

A kis programunk dizájnjával el is készültünk.

Jöjjön a "kódolás"

Fenn, jobb oldalon a designer gomb mellett nyomjuk meg a block feliratú gombot.


Először kattintsunk mondjuk a ball nevű komponensünkre.







Az adott objektumhoz tartozó elemeket három szín különbözteti meg. A barnák az események, a lilák a metódusok (függvények), a zöldek pedig az adattagok (propety-k) lekérdezői, illetve beállítói.
Csapjunk a lovak közé, úgy könnyebb lesz. A labdához tartozik egy edgereached esemény. Amikor a labda eléri valamelyik sarkot, ez az esemény kerül meghívásra. (Hú, ez pongyola picit, meg nem is teljesen igaz ebben a formában, de remélem érthető.)

A labdához tartozik egy bounce (pattanás, ugrás) nevű metódus, ami megváltoztatja a labdánk irányát, paraméterként azt az oldalt kéri, aminek nekiütközött. Az esemény bekövetkeztekor ez az oldal (edge) paraméterként a rendelkezésünkre áll.
Húzzuk ki az edgereached eseményt a kódterületre. Ebbe kell majd "belepattintanunk", hogy mit tegyen, ha ez az esemény bekövetkezik. Még egyszer a labda, és a bounce (lila) metódust húzzuk bele.


Alul a sárga háromszögnél van egy figyelmeztetésünk, mert a bounce nem kapta még meg a paraméterét.





A gettel lekérdezünk, a settel beállítunk, mi most csak le akarjuk kérdezni, hogy melyik oldalnak mentünk. Ha ügyeskedni akarunk akkor megváltoztathatjuk véletlenszerűen az irányát.

Kell a mathból egy összeadás doboz, a labdából egy set heading, egy get heading, meg egy véletlenszám -10től 10-ig.




Ennyitől a labdánk már pattog is :D!

No de nekünk ez nem elég, változzon a sebesség, meg az irány is!

Milyen esemény is kell nekünk?
Az adott gomb lenyomása.
Mit változtatunk meg?
A labda egy tulajdonságát.
Vigyázat! A számoknak is van külön dobozkája! Szintén a math objektumból választható ki, ott van legfelül.


Mindjárt készen vagyunk :D.
Ha rábökünk az elkészült gombeseményünkre jobb gombbal, akkor megduplázhatjuk. Ne essünk kétségbe, hogy azonnal hibát jelez!
Mert a felgombot lecseréljük legombra, a plusz 2-t meg minusz kettőre. Itt jegyezném meg, hogy nem árt egy feltételhez kötni a sebességcsökkentést.



Ha mind a négy gombbal megvagyunk, ez lesz az eredmény:


És készen vagyunk!

Már csak az alkalmazást kell legenerálnunk.

A build menüpontban a generate apk to my computert kiválasztjuk, és

már kész is a friss, ropogós apk-nk, amit feltölthetünk a telefonra, és használhatjuk is.
Ínyencek berakhatnak több labdát, egy dobozt pl. , ami mozog és róla is visszapattan a labda.

Legközelebb egy egyszerű játékot mutatok meg, ami nem sokkal bonyolultabb mint ez, de sokkal látványosabb :D.

Lusta disznóknak itt van az importálható változata annak, amit az imént összekattintgattam. Nem próbáltam ki, nagy valószínűséggel működik, ha nem, akkor vagy én észreveszem, vagy valaki szóljon rám :D.


2016. július 2., szombat

OOP félkész program.

Nyári gyakorlat.

Oop bevezető.

A tizedikeseknek eljött a nyári gyakorlat ideje, meleg is van, a tanulnivaló sem könnyű, arról nem is beszélve hogy - úgy látszik nem hiába -  szajkózom nekik egy ideje, hogy az MIT-n nem tanítanak OOP-t 2011 óta, a közeljövő a funkcionális nyelveké az üzemeltetés miatt, akkor meg minek erőltetem, nemdebár :). (Majd átírjuk ezt szépen ECMASCRIPT-be, nem kell izgulni ;-).)
Sebaj, mostanában úgyis divat az agilis fejlesztés, no meg tizenéves koromban az első kis programjaim egyike volt a kígyós játék - illetve területfoglalós volt tán, amit muszáj volt megírni, mert a kishúgomat érdekelte a suliból engedéllyel hazahozott gép, de csak ha lehet vele játszani -, úgyhogy az elméleti alapok ledarálása után nekiláttunk agilisen kígyós játékot fejleszteni.
A jelen állapot ebből kifolyóan még csak negyedig van készen, már működik ugyan, de ezer sebből vérzik.
Direkt.
Az adattagok nincsenek elrejtve, mindegyik publikus, a zártság egyébként sem teljesül, nincs szétszedve a projekt fájlokra, és még sorolhatnám, illetve sorolni is fogom. Az egyik feladata pont az volt a minap a tisztelt versenyzőknek, hogy pro, illetve kontra hozzanak fel példákat az elvek megvalósulására, illetve meg nem valósulására ebben a nyúlfarknyi programban. Mindenesetre működik már, és éppen ezért rögzítem le ezt az állapotot. Sajnos az idő elszaladt, így azt az állapotot amikor a már működő alkalmazást vizsgálva, ötleteltünk, hogy hogyan is kellene majd kinézni a program végső változatának, milyen funkciókat kell megvalósítani, et cetera (ami miatt jó dolog az agilis fejlesztés, ugye), nem fogja most követni tett, de ami késik, nem múlik.
Konzolos alkalmazás, monodevelop illetve visual studio segítségével fordítható linuxra vagy windowsra, ez alatt a két oprendszer alatt teszteltem, nem tudom hogy máshol fut-e.

Először a végeredmény :).

Nyitunk egy új konzolos alkalmazást, és egyszerűen bemásoljuk ami itt található.
Nincs kiírva hogy milyen billentyűkkel lehet vezérelni, ahhoz tanulmányozni kell a forrást :). (wsad, illetve a kurzorvezérlők két játékosnál,  lustáknak.)
Update: a böngésző lenyelte a List<coord> részt aljas módon, és ezt nem vettem észre eddig. Most már tényleg fut is a galád, ha bemásoljuk. Legalábbis Linux alatt biztos. Windows alatt lehet hogy a terminál mérete miatt elhasal, akkor azt csökkenteni kell. ( Console.SetWindowSize (100, 65); // beégetett érték, agilisek vagyunk, vagy mi :D?
vagy ezt a sort egyszerűen kiremelni és kész.) 

 
using System;
using System.Collections.Generic;

namespace kigyos
{
 public enum irany // felsorolás típus, olvashatóbb a kód, a gépi implementálása tömör
 {
  fel,
  le,
  jobbra,
  balra
 }
 public class coord//public class coord : obj Minden az object őse!!!
 {
  public int x;
  public int y;
  public coord(int x, int y) // konstruktor
  {
   this.x = x;
   this.y = y;
  }
  public override bool Equals(object obj) // példa a virtuális metódusokra
  {
   coord temp = obj as coord;
   return temp.x == this.x && temp.y == this.y;
  }
  public override int GetHashCode() // lusták vagyunk, rövid a lista, nem érdekes a hash annyira
  {
   return base.GetHashCode();
  }
 }
 public class alma // külön osztály az almáknak, lehetne máshogy is
 {
  public char KukacKaja; // a kaja karakteresen
  public coord aktxy; // ez már a javítás része, ha van coord típusunk, használjuk azt
  public int akty;
  public int aktx;
  public int KajaHossz;
  public alma(kigyo[] K, Random r) // ugye milyen jó lenne, ha zárt lenne az objektumunk? Nem kellene paramétereket dobálni
  {   almauj(K, r);   // itt kellene a coord-ot inicializálni :D
  }


  public void almauj(kigyo[] K, Random r)
  {

   this.KukacKaja = '*';
   bool siker = false;
   if (this.aktxy==null ) // nem itt kéne a coord initje, ha már...
    this.aktxy = new coord (0,0);
   do
   {


    aktx = r.Next(1, Console.WindowWidth - 1); // vigyázat! fixme a pálya nem biztos hogy az egész ablakra vonatkozik majd!
    akty = r.Next(1, Console.WindowHeight - 1);// mint fentebb
    aktxy.x=aktx; // javítás kezdete, majd kivezetjük az aktx, akty-t
    aktxy.y=akty;
    siker = true;
    for (int i = 0; i < K.Length; ++i)
    {
     if (K[i].kigyokoords.Contains(this.aktxy))
     {
      siker = false;
     }
    }


   } while (!siker);
   this.KajaHossz = r.Next(1, 4);
   this.KukacKaja = Convert.ToChar (this.KajaHossz+48); // 48=0, ascii!!
   Console.SetCursorPosition(this.aktxy.x, this.aktxy.y);
   Console.Write(this.KukacKaja);
  }
 }
 public class kigyo // kigyóosztály
 {
  public List<coord> kigyokoords; // a koordinátáink listája
  public irany aktirany; // merre nézünk
  public int akthossz; 
  public System.ConsoleColor kigyoszin;
  public char kigyojel;
  public bool utkozott;
  public int aktx; // ez is majd csere lesz a coord típusra
  public int akty;

  public int teteje; // tervezési hiba. az összes kígyóra vonatkozik, majd javítva lesz fixme
  public int alja;
  public int jobbSzel;
  public int balSzel;
  public ConsoleKey gomb_balra; // kell majd customizáló rész, ugye :D
  public ConsoleKey gomb_jobbra;
  public ConsoleKey gomb_fel;
  public ConsoleKey gomb_le;
  public int pont; // ez majd a játékoshoz köthető
  public kigyo(int melyik) // konstruktor hint: honnan is ismerjük fel c#-ban?
  {
   kigyokoords = new List<coord>(50); // helyet foglalunk neki, ne kelljen minden addnál. 
   switch (melyik) // ezért jó a partial class használata, az init feleslegesen foglalja itt a helyet, jobb lenne másik fájlban
   {
   case 1:
    this.aktirany = irany.jobbra;
    this.kigyoszin = System.ConsoleColor.Blue;
    this.kigyojel = 'O';
    this.aktx = 1;
    this.akty = 1;
    this.gomb_balra = ConsoleKey.A;
    this.gomb_jobbra = ConsoleKey.D;
    this.gomb_fel = ConsoleKey.W;
    this.gomb_le = ConsoleKey.S;
    break;
   case 2:
    this.aktirany = irany.balra;
    this.kigyoszin = System.ConsoleColor.Green;
    this.kigyojel = 'X';
    this.aktx = Console.WindowWidth - 1; // fixme, a pályaméret!
    this.akty = Console.WindowHeight - 1;
    this.gomb_balra = ConsoleKey.LeftArrow;
    this.gomb_jobbra = ConsoleKey.RightArrow;
    this.gomb_fel = ConsoleKey.UpArrow;
    this.gomb_le = ConsoleKey.DownArrow;
    break;
   case 3:
    this.aktirany = irany.le;
    this.kigyoszin = System.ConsoleColor.Yellow;
    this.kigyojel = 'M';
    this.aktx = Console.WindowWidth / 2;
    this.akty = 1;
    this.gomb_balra = ConsoleKey.G;
    this.gomb_jobbra = ConsoleKey.H;
    this.gomb_fel = ConsoleKey.T;
    this.gomb_le = ConsoleKey.F;
    break;
   case 4:
    this.aktirany = irany.fel;
    this.kigyoszin = System.ConsoleColor.Yellow;
    this.kigyojel = 'Y';
    this.aktx = Console.WindowWidth / 2;
    this.akty = Console.WindowHeight - 1;
    this.gomb_balra = ConsoleKey.K;
    this.gomb_jobbra = ConsoleKey.L;
    this.gomb_fel = ConsoleKey.I;
    this.gomb_le = ConsoleKey.J;
    break;
   default:
    this.aktirany = irany.fel;
    this.kigyoszin = System.ConsoleColor.Red;
    this.kigyojel = 'E';
    this.aktx = Console.WindowWidth / 2;
    this.akty = Console.WindowHeight / 2;
    this.gomb_balra = ConsoleKey.NumPad4;
    this.gomb_jobbra = ConsoleKey.NumPad6;
    this.gomb_fel = ConsoleKey.NumPad8;
    this.gomb_le = ConsoleKey.NumPad2;
    break;
   } // End Case 
   kigyokoords.Add(new coord(this.aktx, this.akty)); //
   this.akthossz = 5;
   this.utkozott = false;
   this.teteje = 0;
   this.alja = Console.WindowHeight;
   this.balSzel = 0;
   this.jobbSzel = Console.WindowWidth;
  } // End konstruktor
  public void possiblemove(ConsoleKey bill) // fontos metódus, ez állítja be a kígyó irányát
  {
   if (bill == this.gomb_balra && this.aktirany != irany.jobbra)
    this.aktirany = irany.balra;
   if (bill == this.gomb_jobbra && this.aktirany != irany.balra)
    this.aktirany = irany.jobbra;
   if (bill == this.gomb_fel && this.aktirany != irany.le)
    this.aktirany = irany.fel;
   if (bill == this.gomb_le && this.aktirany != irany.fel)
    this.aktirany = irany.le;

  }

  public void kigyomegy() // itt mozog a drága
  {
   char fejmutat=this.kigyojel;
   if (!this.utkozott)
   {
    // itt lehetne átírni a kígyó "fejét" a testére, ha lenne ilyen
    Console.SetCursorPosition (this.aktx, this.akty);
    Console.ForegroundColor = this.kigyoszin;
    Console.Write(this.kigyojel);
    switch (this.aktirany)
    {
    case irany.le:
     this.akty += 1;
     fejmutat='V';
     break;
    case irany.fel:
     this.akty -= 1;
     fejmutat='^';
     break;
    case irany.jobbra:
     this.aktx += 1;
     fejmutat='>';
     break;
    case irany.balra:
     this.aktx -= 1;
     fejmutat='<';
     break;
    }
    kigyokoords.Add(new coord(this.aktx, this.akty)); // a new foglalja a helyet, és csak a címe megy át az add-nak!
    if (this.akthossz <= kigyokoords.Count)
    {
     Console.SetCursorPosition(kigyokoords[0].x, kigyokoords[0].y);
     Console.Write(' ');
     kigyokoords.RemoveAt(0); //ha már elérte a hosszát, akkor az utolsó 8a listában első!!9 elem kuka 
    }
    Console.ForegroundColor = this.kigyoszin;
    if (this.akty <= this.teteje || this.akty >= this.alja || this.aktx <= this.balSzel || this.aktx >= this.jobbSzel) {
     this.utkozott = true;

    } else {
     Console.SetCursorPosition (this.aktx, this.akty);
     Console.Write(fejmutat);

    }

   }
  }

 }
 class MainClass
 {
  public static void Main(string[] args)
  {
   Random r = new Random(); // sajna csak pszeudorandomunk van, ezért a globális objektum
   ConsoleKey ch;
   DateTime starttime = DateTime.Now;
   Console.SetWindowSize (100, 65); // beégetett érték, agilisek vagyunk, vagy mi :D?
   Console.CursorVisible=false;
   int szam = 0;

   do
   {
    Console.WriteLine("Hányan szeretnének játszani(max5) ?");
    try // a kivételkezelés is téma, de csak itt szerepel most
    {
     szam = int.Parse(Console.ReadLine());
    }
    catch (Exception e)
    {
     Console.WriteLine("Kérem számot adjon  meg!! ");
     Console.WriteLine(e.Message);
    }

   } while (szam > 5 || szam < 1);
   int kszama = szam;
   int almaszama = 1 + kszama / 3; // kinek mennyi alma kell
   Console.Clear(); // persze, ki kellene rajzolni a pályát, de ...
   coord tempkoord = new coord(0, 0);
   alma[] almak = new alma[almaszama]; // egységbe zárás? vagy nem ;)?
   kigyo[] kigyok = new kigyo[kszama]; 
   for (int i = 0; i < kszama; i++) // kígyók init
    kigyok[i] = new kigyo(i + 1);
   for (int i = 0; i < almaszama; i++) // almák init
    almak[i] = new alma(kigyok, r);

   for (int i = 0; i < 200; ) // végtelen ciklus, de teszteléskor volt egy i++, így megállt
   {
    for (int j = 0; j < kszama; j++)
    {
     kigyok[j].kigyomegy();
     tempkoord.x = kigyok[j].aktx;
     tempkoord.y = kigyok[j].akty;
     for (int a = 0; a < almaszama; ++a)
     {
      if (almak[a].aktx == tempkoord.x && almak[a].akty == tempkoord.y) // ha megettük az almát :D
      {
       kigyok[j].akthossz += almak[a].KajaHossz; // adatelrejtés :)? Vagy nem ?
       almak[a].almauj(kigyok, r);
       //pont++;

      }
     }


    }
    for (int kigyok1 = 0; kigyok1 < kszama; kigyok1++)
    {
     tempkoord.x = kigyok[kigyok1].aktx;
     tempkoord.y = kigyok[kigyok1].akty;
     for (int kigyok2 = 0; kigyok2 < kszama; kigyok2++)
     {
      if (kigyok1 != kigyok2)
      {
       if (kigyok[kigyok2].kigyokoords.Contains(tempkoord)) // emiatt kellett az equals-t átírni a coord objektumban!!!!
       {
        kigyok[kigyok1].utkozott = true;
        //Console.WriteLine ("Ütközött {0}!!", kigyok1);
       }
      }
     }
    }
    starttime = DateTime.Now;
    while (DateTime.Now.Subtract(starttime).TotalMilliseconds < 200)// mágikus érték, fixme csak kétszáz millisecundum elteltével lép ki a billvizsgálatból
    {

     while (Console.KeyAvailable)/*Ha van leütött gomb, akkor lekérdez 
                                                 * if-fel is müködne*/
     {
      ch = Console.ReadKey(true).Key;// poliformizmusra példa a readkey(true)
      if (ch == ConsoleKey.Escape)
      {
       i = 300; // kilép a végtelen ciklusunkból escape esetén
      }
      bool vanmeg = false; // ha mindegyik kígyó ütközött, ez false marad
      for (int j = 0; j < kszama; j++)
       if (!kigyok[j].utkozott)
       {
        kigyok[j].possiblemove(ch);
        vanmeg = true; // ha csak egy is megy még, akkor igaz lesz
       };
      if (!vanmeg)
       i=300;
      
     }


    }

   }
   Console.WriteLine("Vége a játéknak!");
   for (int i = 0; i < kszama; i++) {
    Console.ForegroundColor = kigyok [i].kigyoszin;
    Console.WriteLine ("Az {0} ("+kigyok[i].kigyojel+") kelgyó hossza: {1}", i + 1, kigyok [i].akthossz);
   }
   Console.ReadLine();
  }
 }
}


Mint látható, van rajta mit javítani, de már játszható.


A fejlesztés "első futás" fázisa zárult, most következik a kódtisztítás, a következő futás előkészítése.
Párhuzamosan a kódtisztítás után refaktorálunk, átírjuk az egészet javascriptbe, majd azt is ideillesztem alkalmasint.

Közben történt egy pici javítás az élvezhetőség kedvéért, az itteni kódot is frissítettem.


Az eredeti célkitűzés.

Az elméleti alapok után....
(java és c# összehasonlítása, régi msdn cikk)

Készítsünk egy programot, ami a sokak által jól ismert "kígyós" játékot valósítja meg több játékossal, természetesen objektum orientáltan, úgy hogy minél előbb használatba vehető alkalmazásunk legyen.
A tesztelés miatt eltekintünk néhány szabálytól, egyelőre legyen minden publikus. (Majd megisszuk a levét, de nem probléma.)

Kezdésnek hozzunk létre egy koordináták objektumot, a kígyónkhoz ez úgyis szükséges lesz, mit ad ég a példában is ez szerepelt :D.

public class coord
{
 int x;
int y;


Ha összehasonlítjuk az eddigi késszel, akkor kicsit hiányos, de a kezdetekben ez bőven elég volt. Át tudtuk írni az értékét, és listát is készíthettünk belőle. Apropó lista. Milyen adatszerkezet jó a koordináták tárolására?
A kezdetekben a társaság a tömböt javasolta, de nem akartam elmenni ebbe az irányba, még ha volt is olyan példakód ahol tömbbel oldották meg a feladatot.
Akkor hogyan tanuljuk meg a listákat :)? Ami egyszerűbb is jóval, mint a tömböt ciklussal másolgatni, a méretét vizsgálni, hogy még beleférünk-e, et cetera, et cetera.
Ha nem írunk külön, akkor automatikusan a rendelkezésünkre áll egy konstruktor - ugyanez igaz a destruktorra is, természetesen -, úgyhogy a kezdetekben ez elégnek is tűnt.

A kígyó fejét külön is számon tartottuk - ezt nem ártott volna coord típussal elvégezni, ha már volt nekünk olyan -, kényelmes.
Ha már gyakorlottak lesznek a népek, a most várható senyvedés - a refaktorálás szépségei - miatt több odafigyelés igényelnek majd a tervezésnél, de jobb egy kis projektnél szembefutni a tipikus hibákkal szerintem. Ha hibának lehet nevezni, hogy objektumfa építés előtt implementálunk, tesztelünk. (Túl sok kód került ki a főprogramba, a tetejében fontos kódok. Ez nyilván nem maradhat úgy, pláne a következő lépcsőfoknál, amikor hálózatosra bővítjük ki a programot.)

-- Majd innen folytatom, most sleep :D.
Elég sokat aludtam közben :).

Az első teljesen kész objektumunk.

 Jó, csak majdnem :).
A koordináták nem véletlenül szerepelnek annyi tutorialban. A hagyományos (struktúrált) programozási nyelvekben nem "divat" gyárilag az ilyen összetettebb adatstruktúra. (Ínyencek persze tudják nagyon jól, hogy a 80-as évekbeli PL/1 (program language 1, egyes számú programozási nyelv, ahogy az IBM szerényen elnevezte ;)) tudott mátrix szorzást is, meg kezelte a komplex számokat is, de nem ez volt a jellemző, lássuk be.

 
public class coord//public class coord : obj Minden object őse!!!
 {
  public int x; // nincs még adatelrejtés!
  public int y; // majd később...
  public coord(int x, int y) // konstruktor
  {
   this.x = x;
   this.y = y;
  }
  public override bool Equals(object obj) // példa a virtuális metódusokra
  {
   coord temp = obj as coord;
   return temp.x == this.x && temp.y == this.y;
  }
  public override int GetHashCode() // lusták vagyunk, rövid a lista, nem érdekes a hash annyira
  {
   return base.GetHashCode();
  }
 }

Azért csak majdnem, mert továbbra sem rejtettük el az adatokat, mint említettem volt, ez később jön sorra. Létrehoztunk egy konstruktort, paraméterekkel. Figyeljük meg, hogy innentől kezdve sikít a fordító, ha paraméter nélkül akarunk létrehozni koordináta objektumot.

Miért van ott az a két metódus alatta?


Ezt nagyon fontos megérteni. Két objektum egyenlősége nem annyira triviális, mint ahogy azt mi gondolnánk. Vajon a 0.0000000000001 és a 0 egyenlőek? Attól függ, nemdebár. Sokan esnek/estek bele abba a csapdába, hogy nem veszik figyelembe a számábrázolás miatti eltéréseket. Ezen túl is számos olyan eset képzelhető el, amikor két "valamit" (objektumot) egyformának ítélünk meg, akkor is, ha a bennük tárolt értékek egyébként különbözőek.
Az objektumhierarchia előnyei itt mutatkoznak meg. Tudjuk hogy számos esetben szükségünk van arra, hogy két objektum egyezőségét megvizsgáljuk. Kedvünk persze nincs arra, hogy ezt minden esetben felüldefiniáljuk, de nem is kell. De lehetőségünk van rá.
Minden objektum közös őse az obj osztály.
Abban pedig van egy Equals metódus, ami felüldefiniálható. Ez most számunkra szükséges is, mert alapértelmezetten két objektum akkor azonos, ha szó szerint azonosak, vagyis ugyanazon a memóriatartományon helyezkedik el mindkettő. De nekünk az kell, hogy a két koordinátapár értékei egyezzenek meg.
A paraméterként megkapott - összehasonlítandó - objektumot coord típusúként nézve össze tudjuk hasonlítani az adott objektum (this) x és y koordinátáit a másikkal.

Oké, de mi a túró alatta a GetHashCode?


Képzeljük el, hogy van egy hatalmas termünk, polcokon telis tele adatkazettákkal, az egyszerűség kedvéért minden adatkazettán csak egy "objektummal". Valaki hoz egy új kazettát, kérdés, hogy már rendelkezünk-e egy ugyanolyannal. (Könyvek esetében ezért van szerző, cim, kiadó, kiadás éve, isbn szám, etc. alapján szortírozva minden könyv.) Nézzük végig az egész termet egyesével? Ugye ezt senki sem akarná. Erre való a hash code. Minden egyes kazettáról készítünk valamilyen eljárással (sok van!) egy rövid kivonatot. Csak ezeket a kivonatokat hasonlítjuk össze első körben, így drasztikusan le tudjuk csökkenteni a munkaigényes teljes összehasonlítást.
A mi esetünkben lehetünk lazák, az alapértelmezett hash kód nekünk megfelelő, nincs túl sok elem, ha kell nyugodtan végigmehet a programunk az egész listán, különben is hogyan tudnánk akkor bemutatni azt, hogy a felüldefiniált metódusban használjuk az ősobjektum eredeti metódusát. Ha valaki ügyes, kitalálhat jó hasht. Pl. a két koordináta szorzata jónak tűnik.
Az absztrakt metódusoknál ugyanez lenne a szisztéma, de akkor kötelező felüldefiniálni az absztrakt metódust. (Csúf lenne ha minden esetben meg kellene mondani, mit gondolunk egyenlőség alatt...)

Egy kis alapelmélet.



 
 public enum irany // felsorolás típus, olvashatóbb a kód, a gépi implementálása tömör
 {
  fel,
  le,
  jobbra,
  balra
 }


Említettem, hogy nem fukarkodunk a hibákkal :). "Rendes" programozó lehetőleg angol rövidítéseket használ. Nem biztos hogy magyar programozó olvassa majd a kódunkat, és a programírás alapnyelve az angol. Nekem sem tetszik ha spanyol nyelvű kommenteket kell értelmeznem, pedig elég sokan beszélnek spanyolul. A kínai kommentekről meg ne is beszéljünk ;).
Az irány helyett természetesen a direction lenne a helyes, és az up/down/right/left négyes.

Miért jó az enum (felsorolás) adattípus? 

Olvashatóbb és stabilabb a kódunk tőle, valamint kis helyen is elfér. Például ez elfér két biten akár, ha a fordítónak arra szottyanna kedve. Nem hinném, hogy adott esetben ez így lenne, de előfordulhat.
Fura, nem? Hosszabb a forráskódunk, de tömörebb, gyorsabb a legenerált kód. Jó szívvel ajánlom a programozóvá válni akaróknak, hogy legalább minimális szinten ismerkedjenek meg valamilyen assembly nyelvvel. Nem azt mondom, hogy gépi ciklusokat számolgassanak, meg önmódosító kódokkal bűvészkedjenek, mint mi annak idején a rettenetesen lassú processzorok idején, de nem árt ha tudjuk, mit is művel a processzorunk. (Már csak azért sem érdemes túlságosan belemélyedni a témába, mert a mai modern operációs rendszerek nagyon sok mindent nyújtanak a számunkra, amit azért macerás assembly-ből kezelni.)

Még egy bőbeszédű szerkezet.


 
    switch (this.aktirany)
    {
    case irany.le:
     this.akty += 1;
     fejmutat='V';
     break;
    case irany.fel:
     this.akty -= 1;
     fejmutat='^';
     break;
    case irany.jobbra:
     this.aktx += 1;
     fejmutat='>';
     break;
    case irany.balra:
     this.aktx -= 1;
     fejmutat='<';
     break;
    }


Miért találták ezt ki? Bőven elég erre az if is, miért bonyolítani az ember életét plusz vezérlési szerkezetekkel :)? Az egyik válasz értelemszerűen persze az, hogy így szebb, meg következetesebb a kódom. A switchnél megmondom hogy mit akarok összehasonlítani, nem kell külön gondolkodni rajta. De nem csak ezért hasznos ez. A programjainkban elég régóta nem használunk már ugró utasításokat, pedig a legenerált kódunk szinte másból sem áll. Ez a fajta szerkezet sokkal hatékonyabb kód generálását eredményezi, mintha ifekkel operálnánk. (Extrém esetben itt ki is használhatná a fordító, hogy a felsorolás típusunk elfér két biten.)
Vegyük észre, hogy a koordinátáinkat fix értékkel módosítjuk, nem lenne ez muszáj, ha grafikus felületen mozognának az objektumaink. Abban az esetben persze az Equals-nál nem lenne már elég a teljes azonosság! Ha két xy koordináta elég közel van egymáshoz, már egyenlőnek kellene tekintenünk. (gyök alatt ((x-x0)négyzet + (y-y0) négyzet)) kisebb mint a sugár lenne a korrekt  összehasonlítás ekkor.)

Jöjjön az almaobjektum.

Eredetileg ez készült el legutoljára, de mivel rövidebb, mint a kígyó, itt van a helye.

 
 public class alma // külön osztály az almáknak, lehetne máshogy is
 {
  public char KukacKaja; // a kaja karakteresen
  public coord aktxy; // ez már a javítás része, ha van coord típusunk, használjuk azt
  public int akty;
  public int aktx;
  public int KajaHossz;
  public alma(kigyo[] K, Random r) // ugye milyen jó lenne, ha zárt lenne az objektumunk? Nem kellene paramétereket dobálni
  {   almauj(K, r);   // itt kellene a coord-ot inicializálni :D
  }


  public void almauj(kigyo[] K, Random r)
  {

   this.KukacKaja = '*';
   bool siker = false;
   if (this.aktxy==null ) // nem itt kéne a coord initje, ha már...
    this.aktxy = new coord (0,0);
   do
   {


    aktx = r.Next(1, Console.WindowWidth - 1); // vigyázat! fixme a pálya nem biztos hogy az egész ablakra vonatkozik majd!
    akty = r.Next(1, Console.WindowHeight - 1);// mint fentebb
    aktxy.x=aktx; // javítás kezdete, majd kivezetjük az aktx, akty-t
    aktxy.y=akty;
    siker = true;
    for (int i = 0; i < K.Length; ++i)
    {
     if (K[i].kigyokoords.Contains(this.aktxy))
     {
      siker = false;
     }
    }


   } while (!siker);
   this.KajaHossz = r.Next(1, 4);
   this.KukacKaja = Convert.ToChar (this.KajaHossz+48); // 48=0, ascii!!
   Console.SetCursorPosition(this.aktxy.x, this.aktxy.y);
   Console.Write(this.KukacKaja);
  }
 }

Persze adatelrejtés itt sincs, no meg az almákat és a kígyókat kezelhetnénk egyben is akár, de ez legyen a jövő zenéje.
Sajnos minden lépést nem tudok itt bemutatni, mindenesetre eleinte nem az alma értéke reprezentálta az almát, hanem egy csillag, ez a rész ott árválkodik a kód elején.
Itt már nekikezdtünk a kód tisztításának, ha a koordinátákra van egy külön adattípusunk, akkor használjuk azt, nem?
Régi mondás, hogy oop program készítésénél nagyon fontossá válik az előzetes tervezés. Ha az ember ezt elmulasztja, akkor eléggé durván vissza tud ütni, mint ahogy azt ennél az objektumnál láthatjuk is.
Mivel agilisen fejlesztünk, időnként szükséges "menet közben kereket cserélni", erre példa az aktuális koordináta két megvalósítása, az eredetileg használt nemsokára eltűnik a kódból.
Súlyos hiba, hogy nem foglalkoztunk a zártsággal. (Abból a szempontból viszont, hogy így hamarabb volt futtatható kódunk, korántsem az. Ha kis, jól tesztelhető részeknél követünk el ilyen hibát, az nem feltétlenül akkora nagy gond, a lényeg hogy tudjunk róla, nem helyes amit teszünk, és javítsuk is ki.)
Emiatt paraméteként át kell vennünk a kígyókat tartalmazó tömböt, és a globális random objektumot.
A coord inicializálása sem szép, ha jól emlékszem órán ezt javítottuk is, de az a kód most nincs itt, én meg jó programozó lévén lusta vagyok most ezzel foglalkozni, no meg nem árt ezzel is tisztában lenni, ha egy objektum még nincs inicializálva, azt hogyan is veszem észre ;).
Az adattagjaink nem bonyolultak, az aktuális almát reprezentáló karakter (ne feledjük, lehet hogy újra kell majd rajzolnunk az almát bizonyos esetekben, bár ez is a jövő zenéje csak), az alma koordinátái, no meg az alma "értéke".
Ha létre akarunk hozni egy új almapozíciót, akkor meg kell vizsgáljuk, hogy nem egy kígyó belsejébe sikeredett-e. Érdemes megfigyelni, hogy jelen esetben előfordulhat hogy két alma ugyanott van, de ez nem okoz fennakadást.
A lista adatszerkezet segítségével tudjuk megvizsgálni ezt. A Contains metódus miatt kellett fentebb a coord objektumunk Equals metódusát felülírnunk. Ha nem tettük volna azt meg, akkor soha nem térne vissza igaz értékkel, mert hiába ugyanaz az x és y koordináta, az objektumok fizikailag nem azonosak.
Minden változónak, így az objektumoknak is van címe - ahol elhelyezkedik a memóriában, ha még nincs ilyen, akkor ez a null érték -, valamint értéke. A létrehozott objektumváltozó csak az objektum címét tartalmazza! Egy fizikailag létrehozott objektumra több objektumváltozó is hivatkozhat. Addig az objektumunk "él", ameddig van olyan változó, ami rá hivatkozik. Amennyiben már nincs ilyen, az úgynevezett szemétgyűjtő (garbage collection) szabadítja fel az objektumunk által elfoglalt helyet. Legalábbis a c# esetében és pár más hasonló oop nyelvben így van.

Akkor itt az idő nekiugrani a kígyónak :D.

Először az adattagokat nézzük át.


   public List<coord> kigyokoords; // a koordinátáink listája
  public irany aktirany; // merre nézünk
  public int akthossz; 
  public System.ConsoleColor kigyoszin;
  public char kigyojel;
  public bool utkozott;
  public int aktx; // ez is majd csere lesz a coord típusra
  public int akty;

  public int teteje; // tervezési hiba. az összes kígyóra vonatkozik, majd javítva lesz fixme
  public int alja;
  public int jobbSzel;
  public int balSzel;
  public ConsoleKey gomb_balra; // kell majd customizáló rész, ugye :D
  public ConsoleKey gomb_jobbra;
  public ConsoleKey gomb_fel;
  public ConsoleKey gomb_le;
  public int pont; // ez majd a játékoshoz köthető

Tudom, ismétlem magam, de itt sincsenek még az adatok elrejtve, fujj.
Már az első sorban elénk tűnik a lista.
Erről bővebben itt olvashatunk magyarul, ha valaki az eredeti leírást akarja angolul, a google a barátja ;).
Nekünk azért kényelmes ez a tároló típus, mert könnyen bővíthető, és a contains metódus is pont kapóra jön, valamint az utolsó elemet is fájdalommentesen - programírás szempotjából természetesen - könnyen törölhetjük. Tehát lesz majd egy listánk koordinátákból, ezt a listát kigyokoords-nak nevezzük el.
Az irányról már esett szó a felsorolásoknál, a kígyó aktuális hossza azért kell, hogy addig ne töröljük az utolsó elemet, ameddig ezt a hosszt el nem értük, a kígyó színe, és a kígyójel értelem szerű. Az ütközött logikai adattag is magáért beszél. Az aktuális x és y koordinátát külön is tároljuk. A pálya paraméterei jelenleg minden kígyónál külön szerepel, ez később természetesen változni fog, nem itt van a helye. A vezérlő gombokat sem kell külön magyarázni, esetleg a ConsoleKey típus lehet érdekes, érdemes utána nézni. A játékos pontszámát ebben a pillanatban még nem is használjuk.

Nézzük a kígyónk konstruktorát.
C#-ban egy objektum konstruktorát ellentétben más nyelvekkel nagyon egyszerűen definiáljuk, egyszerűen az objektum nevével megegyező nevű metódust, illetve különböző paraméterek esetén metódusokat hozunk létre. Ha ilyen nincs, alapértelmezetten készít nekünk egyet a rendszer. Az objektum megszüntetésére is készül egy alapértelmezett destruktor, ha mi akarunk ilyet létrehozni, akkor ~ jelet kell tenni a metódus neve elé.
        public kigyo(int melyik) // konstruktor hint: honnan is ismerjük fel c#-ban?
Jó lenne ha egy erőforrásfájlból szednénk az alapértékeket, ez is a későbbiekre vár egyelőre.
Amint ez jól látható, maximum öt kígyóra vagyunk felkészülve jelenleg. Ha az irányítást nem négy, hanem csak két gombbal kellene végezni (balra, jobbra), akkor egy billentyűzeten "elférne" több játékos is, természetesen. 


            kigyokoords = new List<coord>(50); // helyet foglalunk neki, ne kelljen minden addnál. 


Először is létrehozunk egy koordinátákat tartalmazó listát, aminek előre lefoglalunk ötven helyet.
Az inicializáló részből nézzük most a másodikat, hiszen ott van egy kis elrejtett hiba.
            case 2:
                this.aktirany = irany.balra;
                this.kigyoszin = System.ConsoleColor.Green;
                this.kigyojel = 'X';
                this.aktx = Console.WindowWidth - 1; // fixme, a pályaméret!
                this.akty = Console.WindowHeight - 1;
                this.gomb_balra = ConsoleKey.LeftArrow;
                this.gomb_jobbra = ConsoleKey.RightArrow;
                this.gomb_fel = ConsoleKey.UpArrow;
                this.gomb_le = ConsoleKey.DownArrow;
                break;



Bizony, "be van égetve" a pálya mérete. Ezen majd javítani illik. Itt látszik, hogy miért is választottuk a char típus helyett a Consolekey-t. Hálózatos működés esetén pl. kimondottan csak a kurzorvezérlőket fogjuk használni, azt meg a legkönnyebben így azonosíthatjuk. Ez a kódrészlet szerintem magáért beszél.

            kigyokoords.Add(new coord(this.aktx, this.akty)); //
            this.akthossz = 5;
            this.utkozott = false;
            this.teteje = 0;
            this.alja = Console.WindowHeight;
            this.balSzel = 0;
            this.jobbSzel = Console.WindowWidth;


Bármelyiket is választottuk az ötből, a fentiek mindegyikükre vonatkoznak. Innen is látszik, hogy a pályakoordináták szerencsétlen helyen vannak.

Két lényegi metódusunk van még, az egyik a kigyó aktuális irányát állítja be:

        public void possiblemove(ConsoleKey bill) // fontos metódus, ez állítja be a kígyó irányát
        {
            if (bill == this.gomb_balra && this.aktirany != irany.jobbra)
                this.aktirany = irany.balra;
            if (bill == this.gomb_jobbra && this.aktirany != irany.balra)
                this.aktirany = irany.jobbra;
            if (bill == this.gomb_fel && this.aktirany != irany.le)
                this.aktirany = irany.fel;
            if (bill == this.gomb_le && this.aktirany != irany.fel)
                this.aktirany = irany.le;

        }


Itt huncutkodtam egy kicsit, angol nevet adtam a metódusnak, persze nem possible_move lett a neve, annyira ne legyünk már "jók" :D. Ez szerintem teljesen egyértelmű.

Akkor jöjjön a lényeg, a mozgás.


        public void kigyomegy() // itt mozog a drága
        {
            char fejmutat=this.kigyojel;
            if (!this.utkozott)
            {


Bizonyos időközönként hívjuk meg ezt a metódust. Nyilván nem kell csinálni semmit, ha a kígyónk már ütközött.

                // itt lehetne átírni a kígyó "fejét" a testére, ha lenne ilyen
                Console.SetCursorPosition (this.aktx, this.akty);
                Console.ForegroundColor = this.kigyoszin;
                Console.Write(this.kigyojel);


Ahogy a megjegyzésben szerepel, itt lehetne átírni a kígyó fejét a testhez tartozó karakterre. Ezt meg is tesszük szépen. Még nem mozogtunk, az aktx és akty adattag az előző pozíciót őrzi.

                switch (this.aktirany)
                {
                case irany.le:
                    this.akty += 1;
                    fejmutat='V';
                    break;
                case irany.fel:
                    this.akty -= 1;
                    fejmutat='^';
                    break;
                case irany.jobbra:
                    this.aktx += 1;
                    fejmutat='>';
                    break;
                case irany.balra:
                    this.aktx -= 1;
                    fejmutat='<';
                    break;
                }


Ez értelemszerű, nem hinném hogy külön magyarázni kellene.

                kigyokoords.Add(new coord(this.aktx, this.akty)); // a new foglalja a helyet, és csak a címe megy át az add-nak!
                if (this.akthossz <= kigyokoords.Count)
                {
                    Console.SetCursorPosition(kigyokoords[0].x, kigyokoords[0].y);
                    Console.Write(' ');
                    kigyokoords.RemoveAt(0); //ha már elérte a hosszát, akkor az utolsó 8a listában első!!9 elem kuka 
                }


Hozzáadjuk a listához az aktuális elemet. Ha már elértük a hosszunkat, akkor az utolsó elemet töröljük, és a helyére szóközt írunk. Ezt a részt is javítottuk legutóbb, hiszen ha a kígyó belsejében van a vége, nem kell a szóköz írás. Ez legyen házi feladat, nem nehéz megoldani, még az is lehet hogy ideposztolom alkalmasint.

Jó, itt van.
                if (this.akthossz <= kigyokoords.Count)
                {
                    coord koord_temp = kigyokoords [0];
                    kigyokoords.RemoveAt(0); //ha már elérte a hosszát, akkor az utolsó 8a listában első!!9 elem kuka 
                    if (!kigyokoords.Contains(koord_temp) )
                    {
                        //Console.SetCursorPosition(kigyokoords[0].x, kigyokoords[0].y);
                        Console.SetCursorPosition(koord_temp.x, koord_temp.y);
                        Console.Write(' ');
                    }

                }


Előbb kitöröljük, aztán megnézzük, hogy a maradékban benne van-e.

                Console.ForegroundColor = this.kigyoszin;
                if (this.akty <= this.teteje || this.akty >= this.alja || this.aktx <= this.balSzel || this.aktx >= this.jobbSzel) {
                    this.utkozott = true;

                } else {
                    Console.SetCursorPosition (this.aktx, this.akty);
                    Console.Write(fejmutat);

                }


Ha a falnak mentünk, akkor beállítjuk az ütközött változót, ha nem, akkor kiírjuk az aktuális pozícióra az iránynak megfelelő jelet. Hint : a possible_move -nál ezt a jelet változtathatnánk alkalmasint.


Most már csak a főprogram maradt hátra, de ez ismételten a későbbiekre vár.

Addig is még egyszer a teljes kód, mert a fenti nem a legutolsó, ahogy elnéztem.


 
using System;
using System.Collections.Generic;

namespace kigyos
{
 public enum irany // felsorolás típus, olvashatóbb a kód, a gépi implementálása tömör
 {
  fel,
  le,
  jobbra,
  balra
 }
 public class coord//public class coord : obj Minden object őse!!!
 {
  public int x;
  public int y;
  public coord(int x, int y) // konstruktor
  {
   this.x = x;
   this.y = y;
  }
  public override bool Equals(object obj) // példa a virtuális metódusokra
  {
   coord temp = obj as coord;
   return temp.x == this.x && temp.y == this.y;
  }
  public override int GetHashCode() // lusták vagyunk, rövid a lista, nem érdekes a hash annyira
  {
   return base.GetHashCode();
  }
 }
 public class alma // külön osztály az almáknak, lehetne máshogy is
 {
  public char KukacKaja; // a kaja karakteresen
  public coord aktxy; // ez már a javítás része, ha van coord típusunk, használjuk azt
  public int akty;
  public int aktx;
  public int KajaHossz;
  public alma(kigyo[] K, Random r) // ugye milyen jó lenne, ha zárt lenne az objektumunk? Nem kellene paramétereket dobálni
  {   almauj(K, r);   // itt kellene a coord-ot inicializálni :D
  }


  public void almauj(kigyo[] K, Random r)
  {

   this.KukacKaja = '*';
   bool siker = false;
   if (this.aktxy==null ) // nem itt kéne a coord initje, ha már...
    this.aktxy = new coord (0,0);
   do
   {


    aktx = r.Next(1, Console.WindowWidth - 1); // vigyázat! fixme a pálya nem biztos hogy az egész ablakra vonatkozik majd!
    akty = r.Next(1, Console.WindowHeight - 1);// mint fentebb
    aktxy.x=aktx; // javítás kezdete, majd kivezetjük az aktx, akty-t
    aktxy.y=akty;
    siker = true;
    for (int i = 0; i < K.Length; ++i)
    {
     if (K[i].kigyokoords.Contains(this.aktxy))
     {
      siker = false;
     }
    }


   } while (!siker);
   this.KajaHossz = r.Next(1, 4);
   this.KukacKaja = Convert.ToChar (this.KajaHossz+48); // 48=0, ascii!!
   Console.SetCursorPosition(this.aktxy.x, this.aktxy.y);
   Console.Write(this.KukacKaja);
  }
 }
 public class kigyo // kigyóosztály
 {
  public List<coord> kigyokoords; // a koordinátáink listája
  public irany aktirany; // merre nézünk
  public int akthossz; 
  public System.ConsoleColor kigyoszin;
  public char kigyojel;
  public bool utkozott;
  public int aktx; // ez is majd csere lesz a coord típusra
  public int akty;

  public int teteje; // tervezési hiba. az összes kígyóra vonatkozik, majd javítva lesz fixme
  public int alja;
  public int jobbSzel;
  public int balSzel;
  public ConsoleKey gomb_balra; // kell majd customizáló rész, ugye :D
  public ConsoleKey gomb_jobbra;
  public ConsoleKey gomb_fel;
  public ConsoleKey gomb_le;
  public int pont; // ez majd a játékoshoz köthető
  public kigyo(int melyik) // konstruktor hint: honnan is ismerjük fel c#-ban?
  {
   kigyokoords = new List<coord>(50); // helyet foglalunk neki, ne kelljen minden addnál. 
   switch (melyik) // ezért jó a partial class használata, az init feleslegesen foglalja itt a helyet, jobb lenne másik fájlban
   {
   case 1:
    this.aktirany = irany.jobbra;
    this.kigyoszin = System.ConsoleColor.Blue;
    this.kigyojel = 'O';
    this.aktx = 1;
    this.akty = 1;
    this.gomb_balra = ConsoleKey.A;
    this.gomb_jobbra = ConsoleKey.D;
    this.gomb_fel = ConsoleKey.W;
    this.gomb_le = ConsoleKey.S;
    break;
   case 2:
    this.aktirany = irany.balra;
    this.kigyoszin = System.ConsoleColor.Green;
    this.kigyojel = 'X';
    this.aktx = Console.WindowWidth - 1; // fixme, a pályaméret!
    this.akty = Console.WindowHeight - 1;
    this.gomb_balra = ConsoleKey.LeftArrow;
    this.gomb_jobbra = ConsoleKey.RightArrow;
    this.gomb_fel = ConsoleKey.UpArrow;
    this.gomb_le = ConsoleKey.DownArrow;
    break;
   case 3:
    this.aktirany = irany.le;
    this.kigyoszin = System.ConsoleColor.Yellow;
    this.kigyojel = 'M';
    this.aktx = Console.WindowWidth / 2;
    this.akty = 1;
    this.gomb_balra = ConsoleKey.G;
    this.gomb_jobbra = ConsoleKey.H;
    this.gomb_fel = ConsoleKey.T;
    this.gomb_le = ConsoleKey.F;
    break;
   case 4:
    this.aktirany = irany.fel;
    this.kigyoszin = System.ConsoleColor.Yellow;
    this.kigyojel = 'Y';
    this.aktx = Console.WindowWidth / 2;
    this.akty = Console.WindowHeight - 1;
    this.gomb_balra = ConsoleKey.K;
    this.gomb_jobbra = ConsoleKey.L;
    this.gomb_fel = ConsoleKey.I;
    this.gomb_le = ConsoleKey.J;
    break;
   default:
    this.aktirany = irany.fel;
    this.kigyoszin = System.ConsoleColor.Red;
    this.kigyojel = 'E';
    this.aktx = Console.WindowWidth / 2;
    this.akty = Console.WindowHeight / 2;
    this.gomb_balra = ConsoleKey.NumPad4;
    this.gomb_jobbra = ConsoleKey.NumPad6;
    this.gomb_fel = ConsoleKey.NumPad8;
    this.gomb_le = ConsoleKey.NumPad2;
    break;
   } // End Case 
   kigyokoords.Add(new coord(this.aktx, this.akty)); //
   this.akthossz = 5;
   this.utkozott = false;
   this.teteje = 0;
   this.alja = Console.WindowHeight;
   this.balSzel = 0;
   this.jobbSzel = Console.WindowWidth;
  } // End konstruktor
  public void possiblemove(ConsoleKey bill) // fontos metódus, ez állítja be a kígyó irányát
  {
   if (bill == this.gomb_balra && this.aktirany != irany.jobbra)
    this.aktirany = irany.balra;
   if (bill == this.gomb_jobbra && this.aktirany != irany.balra)
    this.aktirany = irany.jobbra;
   if (bill == this.gomb_fel && this.aktirany != irany.le)
    this.aktirany = irany.fel;
   if (bill == this.gomb_le && this.aktirany != irany.fel)
    this.aktirany = irany.le;

  }

  public void kigyomegy() // itt mozog a drága
  {
   char fejmutat=this.kigyojel;
   if (!this.utkozott)
   {
    // itt lehetne átírni a kígyó "fejét" a testére, ha lenne ilyen
    Console.SetCursorPosition (this.aktx, this.akty);
    Console.ForegroundColor = this.kigyoszin;
    Console.Write(this.kigyojel);
    switch (this.aktirany)
    {
    case irany.le:
     this.akty += 1;
     fejmutat='V';
     break;
    case irany.fel:
     this.akty -= 1;
     fejmutat='^';
     break;
    case irany.jobbra:
     this.aktx += 1;
     fejmutat='>';
     break;
    case irany.balra:
     this.aktx -= 1;
     fejmutat='<';
     break;
    }
    kigyokoords.Add(new coord(this.aktx, this.akty)); // a new foglalja a helyet, és csak a címe megy át az add-nak!
    if (this.akthossz <= kigyokoords.Count)
    {
     coord koord_temp = kigyokoords [0];
     kigyokoords.RemoveAt(0); //ha már elérte a hosszát, akkor az utolsó 8a listában első!!9 elem kuka 
     if (!kigyokoords.Contains(koord_temp) )
     {
      //Console.SetCursorPosition(kigyokoords[0].x, kigyokoords[0].y);
      Console.SetCursorPosition(koord_temp.x, koord_temp.y);
      Console.Write(' ');
     }

    }
    Console.ForegroundColor = this.kigyoszin;
    if (this.akty <= this.teteje || this.akty >= this.alja || this.aktx <= this.balSzel || this.aktx >= this.jobbSzel) {
     this.utkozott = true;

    } else {
     Console.SetCursorPosition (this.aktx, this.akty);
     Console.Write(fejmutat);

    }

   }
  }

 }
 class MainClass
 {
  public static void Main(string[] args)
  {
   Random r = new Random(); // sajna csak pszeudorandomunk van, ezért a globális objektum
   ConsoleKey ch;
   DateTime starttime = DateTime.Now;
   Console.SetWindowSize (100, 65); // beégetett érték, agilisek vagyunk, vagy mi :D?
   Console.CursorVisible=false;
   int szam = 0;

   do
   {
    Console.WriteLine("Hányan szeretnének játszani(max5) ?");
    try // a kivételkezelés is téma, de csak itt szerepel most
    {
     szam = int.Parse(Console.ReadLine());
    }
    catch (Exception e)
    {
     Console.WriteLine("Kérem számot adjon  meg!! ");
     Console.WriteLine(e.Message);
    }

   } while (szam > 5 || szam < 1);
   int kszama = szam;
   int almaszama = 1 + kszama / 3; // kinek mennyi alma kell
   Console.Clear(); // persze, ki kellene rajzolni a pályát, de ...
   coord tempkoord = new coord(0, 0);
   alma[] almak = new alma[almaszama]; // egységbe zárás? vagy nem ;)?
   kigyo[] kigyok = new kigyo[kszama]; 
   for (int i = 0; i < kszama; i++) // kígyók init
    kigyok[i] = new kigyo(i + 1);
   for (int i = 0; i < almaszama; i++) // almák init
    almak[i] = new alma(kigyok, r);

   for (int i = 0; i < 200; ) // végtelen ciklus, de teszteléskor volt egy i++, így megállt
   {
    for (int j = 0; j < kszama; j++)
    {
     kigyok[j].kigyomegy();
     tempkoord.x = kigyok[j].aktx;
     tempkoord.y = kigyok[j].akty;
     for (int a = 0; a < almaszama; ++a)
     {
      if (almak[a].aktx == tempkoord.x && almak[a].akty == tempkoord.y) // ha megettük az almát :D
      {
       kigyok[j].akthossz += almak[a].KajaHossz; // adatelrejtés :)? Vagy nem ?
       almak[a].almauj(kigyok, r);
       //pont++;

      }
     }


    }
    for (int kigyok1 = 0; kigyok1 < kszama; kigyok1++)
    {
     tempkoord.x = kigyok[kigyok1].aktx;
     tempkoord.y = kigyok[kigyok1].akty;
     for (int kigyok2 = 0; kigyok2 < kszama; kigyok2++)
     {
      if (kigyok1 != kigyok2)
      {
       if (kigyok[kigyok2].kigyokoords.Contains(tempkoord)) // emiatt kellett az equals-t átírni a coord objektumban!!!!
       {
        kigyok[kigyok1].utkozott = true;
        //Console.WriteLine ("Ütközött {0}!!", kigyok1);
       }
      }
     }
    }
    starttime = DateTime.Now;
    while (DateTime.Now.Subtract(starttime).TotalMilliseconds < 200)// mágikus érték, fixme csak kétszáz millisecundum elteltével lép ki a billvizsgálatból
    {

     while (Console.KeyAvailable)/*Ha van leütött gomb, akkor lekérdez 
                                                 * if-fel is müködne*/
     {
      ch = Console.ReadKey(true).Key;// poliformizmusra példa a readkey(true)
      if (ch == ConsoleKey.Escape)
      {
       i = 300; // kilép a végtelen ciklusunkból escape esetén
      }
      bool vanmeg = false; // ha mindegyik kígyó ütközött, ez false marad
      for (int j = 0; j < kszama; j++)
       if (!kigyok[j].utkozott)
       {
        kigyok[j].possiblemove(ch);
        vanmeg = true; // ha csak egy is megy még, akkor igaz lesz
       };
      if (!vanmeg)
       i=300;
      
     }


    }

   }
   Console.WriteLine("Vége a játéknak!");
   for (int i = 0; i < kszama; i++) {
    Console.ForegroundColor = kigyok [i].kigyoszin;
    Console.WriteLine ("Az {0} ("+kigyok[i].kigyojel+") kelgyó hossza: {1}", i + 1, kigyok [i].akthossz);
   }
   Console.ReadLine();
  }
 }
}



Kipróbáltam, ez már lefut monodevelop alatt szépen :D.