Kígyókocka grafikus felületen

Kígyókocka

KígyókockaA JavaFX grafikus felhasználói felületének és eseménykezelésének megvalósítása némileg eltér más Java GUI implementációk működésétől, például swing vagy Java3D. Főként animációk során hasznos használni. Megközelítése természetesen objektumorientált: a térbeli objektumok koordinátái, viselkedésük, transzformációkkal valósul meg, és azok is objektumok. A korábban elkészített konzolos kígyókocka programot valósítjuk meg most JavaFX GUI-val.

Ez egy két részből álló blog bejegyzés 2. része. A blog bejegyzés 1. része itt található.

A program működése

Kígyókocka JavaFX grafikus felületen

A program megvalósítása

A start() JavaFX életciklust indító eljárás felépíti a createGridUI() függvényt meghívva felhasználói felületet (színpad/jelenet JavaFX-ben), beállítva a méreteket, címsort, és meghívja az eseménykezelésért felelős handleRotateButtons() eljárást.

A createGridUI() függvény a grafikus felhasználói felület elemeit paraméterezi (szerepe szerint Factory metódus). Öt elemből álló rács ( GridPane osztályú grid nevű objektum) készül el, amelyre nyilakat tartalmazó nyomógombok (pl.: Button típusú btLeft objektum) kerülnek fel a négy égtájnak megfelelően, valamint rajta középen helyezkedik el a kígyókocka 3D megjelenítését megvalósító objektum. A nyilak entitásai az Unikód karaktertáblából származnak. A kígyókocka objektumot a meghívott createSnakeCube() függvény hozza létre. A Node osztályú snakeCube nevű objektum geometriai transzformációs objektumot is hozzá kell rendelni: ez most a négyirányú forgatást megvalósítani képes névtelen Rotate osztályú objektum lesz. A forgatást 5 paraméterrel célszerű beállítani (van rá megfelelő túlterhelt konstruktor), ezek rendre: szög, X, Y, Z tengely origója és a forgatás tengelye. Az objektumok tulajdonosi hierarchiája swing-es szemmel nézve szokatlannak tűnik, de szemléletben legalább azonos a Java3D és a JavaFX megvalósítás.

A createSnakeCube() függvény előállítja a színpadra/jelenetbe kikerülő kígyókockát Node osztályú objektumként. A konstans CUBE tömb egységvektor rendszerben tartalmazza a kígyót alkotó kockák középpontjait. Az első ciklus mindezt nagyítást alkalmazva skálázza. A második ciklus koordináta és pont transzformációk alkalmazásával ( moveToMidPoint: eltolás középre, rotateAroundCenter: forgatás a középpont körül) a kiinduló állapotnak megfelelő méretben és pozícióban elhelyezi a kígyó útvonalát mutató hengerobjektumokat. A konstrukciós és transzformációs műveletek esetén alkalmazkodni kell ahhoz, hogy a JavaFX koordinátarendszerben az X jobbra, az Y lefelé, a Z pedig befelé (a nézőponttól távolodva a térben) növekszik. A matematikai hátteret részletesen most nem magyarázzuk el.

A handleRotateButtons() eljárás a forgatás 4 nyíl eseménykezelésének hozzárendelést végzi el. A nyomógomb objektumok setOnAction() hozzárendelő metódusának paramétere EventHandler funkcionális interfésszel és lambda művelettel működik. A forgatás irányát hozzárendeljük a megfelelő nyomógombhoz. Ez még csak végrendelkezés a jövőre: csak definiáljuk, hogy minek kell majd történnie, ha bekövetkezik az esemény (valamelyik nyílra/nyomógombra kattint a felhasználó).

A rotateSnake() eljárás minden nyíl feliratú nyomógombra kattintva reagál a bekövetkezett eseményre. A rotateAxis objektum a forgatás tengelye, a paraméterként átvett direction enum-tól függ, szinkronban azzal a nyomógombbal, amelyikre kattintott a felhasználó.

Ötletek a továbbfejlesztésre

  • Lehetne többféle irány is, például a négy sarokba átlós vagy mélységi irányú elforgatással.
  • Beépülhetne többféle transzformáció is, például skálázás (kicsinyítés, nagyítás), eltolás (közelítés, távolítás).
  • A kígyó útvonalát mutató hengerobjektumok kirajzolásának sorrendjén lehetne változtatni, mert a megjelenítés nem tökéletes. Jelenleg néhány helyzetben lehetetlennek, Escher lehetetlen konstrukcióihoz hasonlónak tűnhet a kígyókocka. Ha a tér mélységéből a nézőpont felé közeledve rajzolnánk ki a hengerobjektumokat, akkor a 3D látvány nem sérülne.

Tanfolyamainkon JavaFX grafikus felülettel hangsúlyosan nem foglalkozunk, de egy-egy kész forráskódot közösen megbeszélünk, és össze is hasonlítjuk a swing-es változattal. Fejlesztünk játékprogramot, de inkább konzolosan, vagy swing-es ablakban, vagy webes alkalmazásként.

A grafikus felületek felépítésének megismerése fontos lépcső az objektumorientált programozás elmélyítéséhez, gyakorlásához. A grafikus felületekhez egy másik lényeges szemléletváltás is kapcsolódik, hiszen a korábbi algoritmusvezérelt megközelítést felváltja az eseményvezérelt (eseménykezelés).

Tudatosan hangsúlyozott MVC-s projektben megoldva a feladatot, a modell rétegben tárolhatnánk többféle kígyókocka megjelenítéséhez és animációjához szükséges adatszerkezetet és transzformációs objektumokat/metódusokat is és a nézet/vezérlő rétegekben biztosíthatnánk ezek közül a kijelölést/kiválasztást menüvel, ikonokkal, eszköztárral, gyorsbillentyűkkel.

A bejegyzéshez tartozó teljes forráskódot ILIAS e-learning tananyagban tesszük elérhetővé tanfolyamaink résztvevői számára.

Tanfolyamaink orientáló moduljának 9-12. óra: Mesterséges intelligencia alkalmához kapcsolódóan a kígyókocka véletlenszerű előállítása helyett stratégiával rendelkező visszalépéses algoritmust specifikálhatunk és implementálhatunk.

Ez egy két részből álló blog bejegyzés 2. része. A blog bejegyzés 1. része itt található.

Kígyókocka készítése

Kígyókocka

KígyókockaA kígyókocka (snake cube, chain cube) 27 egyforma méretű, egymáshoz képest mozgatható/forgatható kockából áll. A kockákat összeköti egy rugalmas fonal/gumi. Az első és az utolsó kocka egy-egy lapján egy-egy lyuk van. A közbenső kockák hat lapjából kettő lyukas. Fából és műanyagból is készülhetnek. Általában kétféle színnel színezettek a kockák. A cél, hogy a 27 kockát kígyózva „összehajtogatva” a kígyó (lánc) összeálljon egy nagyobb 3x3x3 méretű kockává.

Ez egy két részből álló blog bejegyzés 1. része. A blog bejegyzés 2. része itt található.

A színek – a játék gyártóitól függően – nehézségi szinteket jelölhetnek (zöld, kék, piros, narancs, lila). Léteznek könnyebben és nehezebben megoldható kígyókockák. Előbbieknél többször fordul elő két egymással szemben lévő lyukas lap a közbenső kockákon, utóbbiaknál gyakoribbak az egymással szomszédos lapokon lévő lyukak. Másképpen: előbbiben több a három hosszú egyenes rész, utóbbi szinte állandóan tekereg. Általában a kocka egyik csúcsából kezdjük a megoldást, de az igazán nehéz játékok esetében a kígyó indulhat akár a kocka egyik lapjának (3×3) középső kockájától is.

Vannak olyan kígyókockák, amelyeknek több megoldása is van, azaz többféleképpen is összeállítható kockává. Ezek részletes ismertetése (típusok, gyártók, színek), a megoldások (statikusan és dinamikusan), irányokat mutató jelölésrendszer (Front, Left, Up, Back, Right, Down) elérhető Jaap Scherphuis – holland puzzle rajongó – weboldalán: Jaap’s Puzzle Page.

Kígyókocka

Olyan Java programot készítünk, amely véletlenszerű kígyókockát állít elő.

Tervezés

Szükséges egy háromdimenziós tömb adatszerkezet a kocka tárolására. Több okból is hasznos, ha a tömb mérete 5x5x5. Egyrészt így indexek 0-tól 4-ig futnak és a tömb közepén lévő 3x3x3-as kocka elemei kényelmesen – mátrixszerűen – indexelhetők 1-től 3-ig. Másrészt a tömb közepén lévő 3x3x3-as kocka minden elemére igaz, hogy egységesen van 26 db érvényesen indexelhető szomszédja. A 125 tömbelemből a széleken lévő 98 elem negatív számokkal feltölthető.

A szokásos i, j, k egységvektor rendszerben (koordináta-rendszerben) gondolkodva, i és j a képernyő síkját, k pedig a mélységet jelenti. A k-val 0-tól 4-ig „szeletelve” a tömböt, öt db négyzetet/mátrixot kapunk az alábbiak szerint. A színes tömbelemek negatív számokkal kerülnek feltöltésre, a kígyó útját határolják mindhárom irányból:

Kígyókocka tervezés

A belül lévő – fehér színű – 3x3x3-as kocka/tömb elemeit kezdőértékként célszerű 0-val feltölteni.

A szomszédos kockák kiválasztása során csak a középen lévő kocka 6 db lapszomszédját kell figyelembe venni. A középen lévő (a következő ábrán nem látszó) kocka három tengely szerinti 2-2-2 szomszédos kockája különböző színekkel jelölt.

Kígyókocka tervezés

Él- és csúcsszomszédság esetén nem tud tekeredni a kígyó. A forduláshoz/tekeredéshez legalább 3 – a kígyóban egymás utáni – kocka szükséges. Az aktuális kockának – pozíciójától függően – legfeljebb 6 lapszomszédja lehet. Ezt csökkenti, ha a kocka valamelyik csúcsban helyezkedik el, illetve menet közben is – ahogyan egyre hosszabb lesz a kígyó – folyamatosan csökken a még szabad elemek száma.

A megoldás során a belül lévő – fehér színű – 3x3x3-as kocka/tömb elemeit kell sorszámozni 1-től 27-ig, jelölve ezzel a kígyó útját. A kezdetben 0-val jelölt szabad elemek végül elfogynak.

Megállapodunk abban, hogy a kígyó az útját az (1, 1, 1) pozícióban kezdi és az 1 sorszámot kapja. Addig kell újabb szomszédos kockákat – egyesével haladva – kiválasztani módszeresen vagy véletlenszerűen próbálkozva, vagy akár visszalépéses algoritmussal is, amíg mind a 27 kiválasztásra kerül.

Megvalósítás

Létre kell hozni a háromdimenziós tömböt példányváltozóként:
int[][][] cube=new int[5][5][5];

A cubeInit() metódus kezdetben feltölti a tömb elemeit. A széleken lévő elemekbe különböző negatív értékek kerülnek, hogy jól megkülönböztethető legyen, melyik ciklus melyik pozíciókért felel. Másképpen is lehetne: például kezdetben minden elem -1, utána a belül lévők felülírhatók 0-val.

Hasznos a cubePlot() metódus, amellyel megjeleníthetők a konzolon a tömb elemei (állapota):

A getNextNeighbour() függvény egydimenziós tömbként ( int[]) visszaadja a paramétereként átvett – x, y, z koordinátával jelölt – kocka egyik kiválasztott szomszédját, amely a kígyó útját jelöli. A kiválasztás történhet módszeresen vagy véletlenszerűen próbálkozva, vagy akár visszalépéses algoritmussal is. A metódus forráskódját most nem részletezem. A metódus felelős a kígyó helyes útvonaláért, azaz a kiválasztás során a kígyó nem rekedhet meg zsákutcában, másképpen nem haraphatja meg saját magát.

A vezérlést a run() metódus végzi el az alábbiak szerint:

Addig fut a ciklus, amíg a kígyó nem tölti ki a 3x3x3-as kockát (másképpen: amíg a kígyó nem éri el a maximális hosszúságot). A tömb állapotát kezdetben és lépésenként is kiíratja a vezérlő metódus a konzolra.

Konzolos eredmény

A konzolos eredmény előállításánál fontos volt, a tömb változásait tudjuk követni. Az összes negatív szám elhagyható lehet a kiírásból, ha meggyőződtünk az implementált algoritmus helyes működéséről. Átlátva a problémát, a megoldás könnyen elállítható egy grafikus és/vagy egy irányokat mutató jelölésrendszer szerint is, például:

Kígyókocka tervezés

Hivatkozások

A bejegyzéshez tartozó teljes forráskódot ILIAS e-learning tananyagban tesszük elérhetővé tanfolyamaink résztvevői számára.

A feladat a Java SE szoftverfejlesztő tanfolyam tematikájához kötődik. Több alkalommal is tudunk ezzel a feladattal foglalkozni, attól függően, hogy a getNextNeighbour() függvény működését hogyan tervezzük és implementáljuk:

  • A 13-16. óra: Tömbök témakör esetén a szomszédos kockák közül módszeresen – azonos sorrendben haladva a legfeljebb 6 lehetséges szomszéd közül – választjuk ki mindig az elsőt. Ekkor mindig ugyanazt az egyetlen helyes megoldást kapjuk meg.
  • A 17-28. óra: Objektumorientált programozás témakör esetén atipikusan a kivételkezelést használhatjuk vezérlésre úgy, hogy a lehetséges szomszédos kockák közül mindig véletlenszerűen választunk. Ekkor a kígyó önmagába harapását úgy előzzük meg, hogy tömb túlindexelésekor keletkező kivételt benyeljük és újrakezdjük a feladatot mindaddig, amíg találunk egy olyan megoldást, aminek lépései közben nem keletkezik kivétel.
  • Az orientáló modul 9-12. óra: Mesterséges intelligencia témakör esetén véletlenszerű kocka kiválasztási stratégiával rendelkező visszalépéses algoritmust specifikálhatunk és implementálhatunk. Ez lényegesen összetettebb feladat és mindig helyes megoldást ad több lehetséges megoldás közül.

Ez egy két részből álló blog bejegyzés 1. része. A blog bejegyzés 2. része itt található.

Hogyan értékeljük az online vizsgafeladatot?

értékelés

Tanfolyamaink követelményeinek teljesítéséhez több online tesztet kell kitölteni és egy komplex, online vizsgafeladatot kell megoldani.

A feladatspecifikáció mindig részletes, maximum 1 db A4-es oldal terjedelmű, folyó szövegben felsorolásokat is tartalmaz és szándékosan nincsenek benne ábrák. Törekszünk az egyértelmű megfogalmazása, de hagyunk mozgásteret egyéni értelmezésre is, amit – megfelelő indoklással – elfogadhatunk. Az online vizsgafeladat megoldásához bármilyen segédeszközt lehet használni.

Az online vizsgafeladat megoldásának tervezésére, implementálására, tesztelésére és dokumentálására és határidőre való feltöltésére körülbelül egy hét áll rendelkezésre. Közben online konzultációt biztosítunk, ahol megbeszéljük az ezzel kapcsolatos kérdéseket és rávezető (nem konkrét) segítséget biztosítunk.

vizsgafeladat értékelése

Figyelembe vett szempontok az online vizsgafeladat értékelése során

  • Objektumorientált szemléletmód alkalmazása
  • MVC architektúrális tervezési minta alkalmazása
  • Logikus MVC szeparáció
  • Egyértelműen elhatárolódó felelősségi kör: a modell, a nézet és a vezérlő azt és csak azt oldja meg, amit, ahogyan, amikor, ahányszor kell
  • Adatbázis-kapcsolatért felelős rész szeparációja
  • Vezérlésért felelős rész szeparációja
  • Megjelenítésért felelős rész szeparációja
  • MVC kommunikációs irányok betartása, megfelelő adatkonverzió
  • Szükség esetén singleton és factory típusú tervezési minta alkalmazása
  • Adatbázis-kapcsolat megfelelő menedzselése, nyitás, zárás, kivételkezelés
  • Szükséges adatbázis-karbantartó (CRUD) művelek megfelelő megvalósítása
  • Specifikáció pontos értelmezése
  • Specifikáció pontos megvalósítása
  • Specifikáció alapján tesztelés megvalósítása
  • Megfelelő GUI komponensek alkalmazása, elhelyezése, paraméterezése, kommuniká­ciója, eseménykezelése
  • Adatbázis olvasása során a keletkező eredménytábla és/vagy kivételobjektum megfelelően jut el a nézet réteghez
  • Modellvezérelt fejlesztés elveinek alkalmazása
  • Szükség esetén POJO és ezek adatszerkezeteinek konstrukciós és szelekciós műve­letei
  • Eseménykezelés logikus működésének megtervezése és megvalósítása
  • Extrém tesztadatokkal való hibakeresés, tesztelés
  • Felesleges forráskód-részletek nincsenek
  • Szintaktikai és/vagy szemantikai hibák nincsenek (Java, SQL, HQL oldalon egyaránt)
  • Projekt megfelelő elnevezése és szerkezete
  • Logikus és konvencióknak megfelelő elnevezések következetes alkalmazása
  • Algoritmusban, folyamatokban, saját modellekben való eligazodás, alkalmazkodás ké­pessége, ezek szintjei és megvalósulása
  • Szükséges programozási tételek felismerése, megvalósításuk, összeépítésük
  • Logikus gondolkodás és feladatmegoldás szintjei és alkalmazásuk
  • Hatékonysági szempontok ismerete és alkalmazása

Az online vizsgafeladatot – a tanfolyamot záró 53-56. óra: Összefoglalás alkalommal – közösen, részletesen meg is beszéljük: lépések, rétegek, funkciók, ellenőrzési/tesztelési lehetőségek, hibakeresés, tipikus problémák a megoldás során.

vizsgafeladat értékelése

CHOO + CHOO = TRAIN

Most nem a híres kisvonatról van szó, hanem egy ismert kriptoaritmetikai feladványról. Ebben a feladattípusban egyszerű matematikai műveletek szerepelnek és a különböző betűk különböző számjegyeket jelölnek. Általában többféleképpen megoldhatók: intuíció, ötlet, módszeres próbálkozás, következtetés, kizárás vagy klasszikus behelyettesítés. Ha van megoldás és meg is találunk egyet, akkor a következő kérdés az, hogy van-e még, illetve összesen hány megoldás van?

Íme a feladat:

Érdemes minden megoldás során figyelembe venni a minden számjegyet 0-9-ig végigpróbáló lépések helyett legalább az alábbi öt feltételt:

  • C >= 5, hiszen CHOO olyan négyjegyű szám, aminek a kétszerese ötjegyű szám,
  • T = 1, mivel két négyjegyű szám összege 10000 < TRAIN < 20000 (ebben az esetben),
  • O >= 6, hiszen maradéknak képződnie kell, mert I és N különbözik,
  • 2 <= N < I és
  • I >= 3 és szintén a maradékképződés miatt.

Esetleg még tovább gondolkodva, felfedezhetünk egyéb összefüggéseket, illetve kizárhatunk egyéb értékeket, így jelentősen csökkenthető egy-egy Java implementáció lépésszáma.

1. megoldás

Ez adatszerkezet nélküli megoldás, így eléggé összetett feltétellel valósul meg a művelet teljesülése (megfelelő helyiértékek használatával) és a különbözőségek vizsgálata.

2. megoldás

Itt az ellenőrzési feltétel egyszerűbb, mert a különbözőség/egyediség tesztelését áthárítottam a halmazszerűen működő HashSet generikus kollekcióra, építve annak beépített képességére.

Mit gondolsz, melyik megoldás hajtódik végre gyorsabban? Miért?

Mivel a két megoldásnál a ciklusok szervezése megegyezik, így a használt adatszerkezet dönt (hiszen annak konstrukciós és szelekciós, azaz karbantartási műveletei vannak). Az 1. megoldás a gyorsabb, mert nem használ adatszerkezetet. A 2. megoldás lényegesen lassabban fut, mert a generikus kollekció műveletei miatt az automatikus szemétgyűjtő tevékenység erősen igénybe vett. A különbség nagyságrendileg 15-szörös.

A feladatnak két megoldása van: 5466 + 5466 = 10932 és 5488 + 5488 = 10976.

A bejegyzéshez tartozó teljes – időméréssel kiegészített – forráskódot ILIAS e-learning tananyagban tesszük elérhetővé tanfolyamaink résztvevői számára.

Akinek kedve támadt, lásson hozzá hasonló feladatokhoz:

A feladat a Java SE szoftverfejlesztő tanfolyam szakmai moduljának 9-12. óra: Metódusok, rekurzió alkalomhoz, illetve a 21-24. óra: Objektumorientált programozás, 2. rész alkalomhoz kötődik.

Munkakör, létszám, névsor lekérdezése

Munkakör, létszám, névsor

Munkakör, létszám, névsorAz a fela­da­tunk, hogy az Oracle HR sé­má­ból le­kér­dez­ve állít­suk elő munka­kö­rön­ként cso­por­to­sít­va az al­kal­ma­zottak lét­szá­mát és név­so­rát. Adott a JOBS és az EMPLOYEES táb­lák kö­zötti 1:N kap­cso­lat. A JOBS táb­lá­ban (szó­tár) lé­vő JOB_ID egye­di kulcs­hoz tar­to­zik egy hosszabb szö­ve­ges JOB_TITLE le­í­rás (mun­ka­kör), va­la­mint az EMPLOYEES táb­lá­ban meg­ta­lál­ha­tó a JOB_ID kül­ső kulcs­ként. Az EMPLOYEES táb­lá­ban elér­he­tő az al­kal­ma­zottak neve: FIRST_NAME és LAST_NAME. Min­den mun­ka­kört be­tölt leg­alább 1 al­kal­ma­zott és min­den al­kal­ma­zott­hoz van hozzá­ren­delt mun­ka­kör.

Oracle HR séma

Tanfolyamainkon többféleképpen modellezzük és tervezzük meg a feladat megoldását.

Megoldás (Java SE szoftverfejlesztő tanfolyam)

A Java SE szoftverfejlesztő tanfolyam 45-52. óra: Adatbázis-kezelés JDBC alapon alkalmain a következők szerint modellezünk és tervezünk.

Kiindulunk az alábbi egyszerű SQL parancsból:

Munkakör-létszám-névsor-SQL-1

Eredményül ezt kapjuk (részlet):

Munkakör, létszám, névsor eredmény 1

A kapott eredménytáblát a Java kliensprogram fejlesztése során leképezzük egy generikus POJO listába, a rekordonként összetartozó 3 adatból előállítva az objektumok tulajdonságait. A generikus listát csoportváltás algoritmussal feldolgozva, könnyen listázzuk a létszámot és a névsort munkakörönként csoportosítva. A munkakörönkénti létszámot a listafeldolgozás során megkapjuk. Ezt most nem részletezzük, de tanfolyamaink hallgatói számára ILIAS e-learning tananyagban tesszük elérhetővé a teljes forráskódot. Ennél a megoldásnál egyszerűbb a lekérdező parancs, de összetett az eredmény feldolgozása.

Megoldás (Java adatbázis-kezelő tanfolyam)

A Java adatbázis-kezelő tanfolyam 9-12. óra: Oracle HR séma elemzése, 13-16. óra: Konzolos kliensalkalmazás fejlesztése JDBC alapon, 1. rész, 33-36. óra: Grafikus kliensalkalmazás fejlesztése JDBC alapon, 2. rész alkalmával a következők szerint modellezünk és tervezünk.

Denormalizált eredményt közvetlenül visszaadni képes összetett SQL parancsot készítünk:

Munkakör, létszám, névsor SQL-2

Eredményül ezt kapjuk (részlet):

Munkakör, létszám, névsor, eredmény-2

A kapott eredménytáblát a Java kliensprogram fejlesztése során közvetlenül kiíratjuk, hiszen minden szükséges adatot tartalmaz. Az utolsó oszlopban összefűzve megkapjuk az adott részleghez tartozó alkalmazottak névsorát. Ezt most nem részletezzük, de tanfolyamaink hallgatói számára ILIAS e-learning tananyagban tesszük elérhetővé a teljes forráskódot. Ennél a megoldásnál összetettebb a lekérdező parancs, de egyszerű az eredmény feldolgozása.

Érdemes átgondolni és összehasonlítani a kétféle különböző megközelítés lehetőségeit, korlátait. Ha egyensúlyozni kell a kliensprogram és az adatbázis-szerver terhelése között, valamint az MVC modell összetettsége, karbantarthatósága, könnyen dokumentálhatósága a/is szempont, akkor többféle alternatív módszer is bevethető, valamint építhetünk a különböző Oracle verziók (dialektusok) képességeire is.

Az SQL forráskódok formázásához a Free Online SQL Formatter-t használtam.