Vécsey Zoltán JATE szakdolgozat

“Játszik a gyerek, a kismacska, a színész, a muzsikus, a sakkozó,

a kártyás, a lóversenyfogadó, a tőzsdei spekuláns.

Játszik a könnyelmű a tűzzel, a vakmerő az életével.

Játszik a mérleg nyelve, a plüss anyag színe.

’Játszik öreg Földünk fiatal sugarával a Napnak’ (Petőfi).

Játszik a leány a legénnyel, ’mint macska szokott az egérrel’

 

Életünknek nincs olyan szakasza, a történelemnek olyan kora, a tudománynak, művészetnek olyan ága, a társas életnek olyan alkalma, amelyben ne jutna szóhoz valamilyen módon a játék. Nincs olyan jelensége a természetnek és az emberi civilizációnak, amelyből ne lehetne játékot készíteni. Ha nem is éppen társasjátékot, leginkább színjátékot.

Majd ha letelik ez az évszázad, s a történészek nekilátnak, hogy portrét rajzoljanak róla, sok más fontos vonása mellett arról sem feledkezhetnek meg, hogy a játékok százada volt. A játékszereké — amelyek fantasztikus bőséggel és változatossággal árasztják el a világot — de még inkább az intellektuális játékoké.

A régebbi korok költői, muzsikusai, tudósai is belefelejtkeztek néha a játékba, de inkább csak pihenésül, “igazi” munkájukból ellopott órákban. S az iskolakerülő diák rossz lelkiismeretével.

Korunkban — szeretnénk hinni — csökken a becsülete a nagyképű komolyságnak, a játék árfolyama viszont emelkedik. Mindenekelőtt a matematikusoknál. Játékok adtak ötletet egy sereg igen fontos matematikai tétel, sőt elméleti rendszer kialakításához. S a matematika tételeiből új játékötletek születtek.

A matematikai játékok adtak ösztönzést másfajta intellektuális játékok kigondolására, továbbfejlesztésére. A nyelvészek a szavakkal játszanak, a pszichológusok a tesztekkel, a képzőművészek és zenészek a technika kínálta új effektusokkal.

Játszunk a kamerák előtt, a tv képernyők mögött, játszunk az interneten és a számítógépeken. Gyűjtjük a pontokat és tapasztalatokat, mind nyerni szeretnénk.

 

Tartalomjegyzék

1. Bevezetés a játékelméletbe *

1.1. Ismertető *

1.2. Egy történelmi példa *

1.3. Összehasonlítás és tanulságok *

1.4. Elfogult megjegyzések a módszerről *

1.5. Játékosok és személyek *

1.6. A nyereség *

1.7. Stratégiák *

1.8. A kritérium *

2. Táblás játékok *

2.1. A táblás játékok sokszínűsége *

2.2. Játékosok száma *

2.2.1. Egyszemélyes játékok *

2.2.2. Kétszemélyes játékok *

2.2.3. Többszemélyes játékok *

2.3. A résztvevő bábúk száma *

2.3.1. Azonos számú és rangú bábúk *

2.3.2. Eltérő erőviszonyok, előnyadós játékok *

2.3.3. Változó bábú szám *

2.4. Jellegzetes lépések *

2.4.1. Egyedi lépésszabályok *

2.4.2. “Átugrálós” *

2.4.3. “Szaporodás-hódítás” *

2.4.4. “Malom” játékok *

3. A halma játék elkészítése JAVA nyelven *

3.1. A játék szabályai *

3.2. A program tervek *

3.3. A pálya kialakítása *

3.4. A manók elhelyezése *

3.5. Lépések ellenőrzése *

3.6. “Lépéstávolság” *

3.7. Beértünk-e? *

3.8. Ellenfél, a stratégia *

3.8.1. Mi az elérendő cél? *

3.8.2. Memória vagy gondolkodás? *

3.8.3. Lépéslehetőségek keresése, mohó algoritmus *

3.8.4. Rekurzív algoritmus *

3.8.5. További lehetőségek? *

3.8.6. Tesztelés *

3.9. Grafika *

3.10. Kezelőfelületek és egyéb rutinok *

3.11. A program életciklusa *

4. Mellékletek *

4.1. Rendszer igények *

4.2. Ábrák a gépi lépés szemléltetésére *

4.3. Irodalomjegyzék *

 

  1. Bevezetés a játékelméletbe
    1. Ismertető
    2. Az ismertetésre kerülő módszer neve játékelmélet. Akinek sok ideje van, az a stratégiajátékok elmélete elnevezést is használhatja. Aki először találkozik ezzel a valószínűtlenül hosszú elnevezéssel, feltehetően megkérdezi, hogy: Miért? Válaszunk a következő: az elnevezés onnan ered, hogy a stratégia elméletének alapja a játékok vizsgálata. A válasz valójában nem kielégítő, újra feltehetjük a kérdést:

      Miért? Azért, mert a játékokban megtalálhatók a különféle konfliktusok főbb elemei, leírásuk és tanulmányozásuk viszonylag egyszerű. ( A “játék” szó használata az elmélet szempontjából nem lényeges. Szerepe csak annyi, hogy az összes vizsgált konfliktust ezzel a névvel illetjük.)

      Ennek alátámasztására gondoljuk végig, hogy mi a közös mondjuk a póker játékban és egy háborús konfliktusban. A póker játékban a játékosok egy bizonyos jutalmazási rendszer szerint játszanak. A játékosok érdeke nyilván ellentétes: mindegyikük nyerni szeretne, de ha az egyik nyer, akkor mások szükségképpen veszítenek. Ez a konfliktus alapja. Az egyik játékos szemszögéből nézve vannak olyan elemei a játéknak, amelyek teljesen az ő ellenőrzése alatt állanak. Ilyen például az, hogy kiválaszthatja, kikkel óhajt játszani. Ez azonban a többi játékosra is igaz. Vannak tehát a játéknak olyan elemei, amelyek ellenőrzése teljesen az ellenfél kezében van. Végül vannak olyan elemek, amelyek ( ha a szabályokat betartják ) egyik játékos ellenőrzése alatt sincsenek.

      Ilyen például a lapok sorrendje. Felfoghatjuk úgy, hogy ezek az elemek a Természet nevű hosszú életű, erős személyiség ellenőrzése alatt állnak. A Természet tudatosan nem rosszindulatú, de pajkos könnyedséggel fogja fel a saját komoly érdekeit. A leírt szempontok bizonyosan megtalálhatók minden konfliktusban.

      Fontos jellemvonás a mindenkori információ ( katonai nyelven hírszerzési anyag ) szerepe. Az információ gyakran nem teljes és ezért kényelmetlen tényező. Nem tudhatjuk például, hogy ellenfelünk kezében milyen lapok vannak. Blöff is elképzelhető. Más hasonlatosságok is vannak ( előfordulhat, hogy valaki gyilkosság áldozata lesz J )

      Az analógiát azonban nem vihetjük túl messzire. A póker játék sok mindenben nem tükrözi a hadviselést. A háborúban egy tank legyőzhet két tankot, a pókerben viszont a kártyalapok leterítésénél egy pár bubi mindig jobb, mint egy ász. Módosíthatjuk persze a póker játékot úgy, hogy jobban hasonlítson a tankok csatájához. Felállíthatunk olyan szabályt, hogy egy ász rosszabb ugyan akármelyik párnál, de ha a játék közben telefonál valakinek a felesége, akkor a két bubinál jobb. Ennek azonban nem volna különösebb értelme, ugyanis a játékok vizsgálata a stratégia tanulmányozásának éppen ezért jó kiindulópontja, mert a játékok a háborús konfliktusok és az egyéb jelenségek bonyolult helyzeteinek összességét nem tartalmazzák. Az elméletek, fejlődésük korai szakaszában nem képesek arra, hogy a jelenségek vizsgálatánál az összes közrejátszó tényezőt figyelembe vegyék.

      Az eddigiek alapján talán világos, hogy a játékok tartalmazhatnak olyan alapelemeket, amelyek majdnem minden érdekes konfliktusban megtalálhatók. Következik-e ebből, hogy hasznos dolog tanulmányainkat játékok vizsgálatával kezdeni? Nem szükségképpen. Elképzelhető az is, hogy a háborús-, gazdasági- és társadalmi helyzetek felépítése annyira bonyolult, hogy nem közelíthetők meg a játékok fogalmából kiindulva. Ezt a lehetőséget azért kell vizsgálnunk, mert a játékelmélet jelenlegi formájában még a valódi játékok mindegyikének leírására sem képes, sőt, kénytelenek vagyunk nagyon egyszerű valódi játékok és a bonyolultabb játékok ( például a póker ) leegyszerűsített változatainak vizsgálatára szorítkozni.

      Ilyen körülmények között nevetségesnek tűnhet, hogy valaki számottevő energiát fordít a játékelmélet tanulmányozására és fejlesztésére, sőt azt várja, hogy mások is érdeklődjenek a tárgy iránt. Ennek okát részben abban találhatjuk meg, hogy a játékelmélet eddigi sikerei reménnyel és a tárgy iránti hűséggel töltötték el az elmélettel foglalkozó tudósokat. A szándékosan túlságosan leegyszerűsített elméletek kitalálása a tudományok egyik fontos módszere, amelyet különösen az “egzakt” tudományok terén alkalmaznak sokszor. Ha a biofizikusok eredményesen használhatják a sejt, a csillagászok pedig a világegyetem leegyszerűsített modelljét, akkor mi is remélhetjük, hogy a leegyszerűsített játékokat nagyon jól alkalmazhatjuk bonyolultabb konfliktusok modelljeiként.

      Nyilvánvaló, hogy az ilyen elméletek körében a halandóság nagyobb, mint amekkorát egy katonai szervezet eltűrhet saját megmozdulásai során. Az elmélet sikerei nyilván nem örökéletűek. A legtöbb, amit tőle várhatunk az, hogy napjainkban bizonyos meghatározott célokra sikeresen alkalmazhatjuk.

    3. Egy történelmi példa
    4. Vizsgáljuk meg egy példán, hogy mit várhatunk a sikeres tudományos absztrakcióktól. Olyan elméletet választunk, amely biztosan a valóság nagyon leegyszerűsített modelljét vizsgálja.

      Tegyük fel, hogy a Naprendszer bolygóinak mozgását akarjuk tanulmányozni. Tekintsük az egyes bolygókat olyan pontoknak, amelyek tömege megegyezik a bolygókéval. Tegyük fel, hogy bármely két pont között olyan kölcsönös vonzóerő lép fel, amelyet úgy számíthatunk ki, hogy a két pont tömegének szorzatát elosztjuk távolságuk négyzetével, és ezt megszorozzuk egy állandó értékkel, továbbá minden egyéb körülménytől eltekintünk. Tételezzük fel még azt is, hogy az elmélet nem nyilvánvaló ostobaság, hiszen ha az volna, akkor el sem kezdenénk vizsgálni.

      Valójában ez az elmélet, a gravitáció elmélete, két és fél évszázadon át megfelelő volt a bolygók mozgásának leírására. Az elmélet a Merkúr pályáját adta meg a legnagyobb hibával, a Merkúr ugyanis a várt helytől egy évszázad alatt a radián ötezred részével tért el. Az Einstein által javított elmélet már figyelembe veszi az ilyen hiányosságokat.

    5. Összehasonlítás és tanulságok
    6. A gravitáció elmélete természetesen nem annak az almának köszönheti megszületését, ami állítólag Newton fejére esett. A bolygók mozgásáról, nagyrészt Tycho Brahe munkásságának eredményeképpen, már korábban is sokat tudtak. Kepler felismerte a bolygók mozgásának néhány alapvető törvényét. Newton ezek felhasználásával és a matematika egy új ágának ( az analízisnek ) kidolgozásával talált rá a fenti absztrakcióra. Balszerencséjére éppen a Hold mozgásának vizsgálatával akarta kipróbálni az elméletét. A súlyosan hibás adatok miatt többévi munkája ment veszendőbe.

      A példákból számos tanulságot szűrhetünk le. Egyrészt elképzelhető, hogy viszonylag bonyolult jelenségeket nagyon egyszerű elmélettel lehet magyarázni. Aki felteszi, hogy a bolygók mozgása ilyen egyszerű, az sosem vállalhat felelősséget jóslataiért. Másrészt tanulság az, hogy ha egy elmélet nagyon általános, akkor rendszerint a jelenségek széles körében eredményesen alkalmazható: a gravitáció elmélete például nemcsak a bolygók mozgására, hanem bármely, tömeggel rendelkező részecskére érvényes. A harmadik tanulság. az, hogy az elméletek gyakran nem tökéletesen, de néha ( például a fenti esetben ) szinte megtévesztő pontossággal írják le a valóságot. Újabb, nagyon fontos tanulság az, hogy az elmélet a testek mozgását meghatározó tényezők közül csak egyet ragad ki, ráadásul olyat, amelyik gyakran elhanyagolható. Például két, szorosan egymás mellett haladó repülőgép között a gravitációs erő körülbelül akkora, mint egy cigarettapapír súlya.

      Tanulság az is, hogy lényeges adatok birtokában kell lennünk. Ilyen szempontból Newton jobb helyzetben volt, mint azok, akik a konfliktusok vizsgálatának területén szeretnének eredményeket elérni. Az adatok nagy részét olyan tényezők határozzák meg, mint a személyiség ( tehát az egyén fizikai, érzelmi beállítottsága, egészségi állapota, különféle képességei stb. ) és a társadalmi környezet. Az ilyen jelenségek tanulmányozása pedig nehéz feladatot ró a matematikusokra.

      Újabb tanulság, vagy legalábbis sokatmondó megjegyzés az, hogy Newton csaknem egyidejűleg dolgozta ki a gravitáció elméletét és a matematika új ágát, az analízist, és az, hogy az analízis nélkül a gravitáció elmélete gyakorlatilag használhatatlan lett volna. Ezen túlmenően az analízis kétszázötven éve lényeges szerepet játszik a fizikában. Korai lenne még arról beszélni, hogy a játékelmélet is a matematika egy új ágának létrejöttét eredményezheti. A játékelmélet eddig nagyon kevés újat hozott, szinte mindent a matematika már kidolgozott ágaiból kölcsönzött. Lehetséges, sőt talán szükségszerű, hogy a módszer fejlődésével a későbbiekben új matematikai ág születik. Mindenesetre érdemes megjegyezni, hogy a játékelmélettel kapcsolatos első eredmények korunk egyik nagy matematikusának és sokoldalú gondolkodójának, Neumann Jánosnak köszönhetők.

      Neumann játékelmélettel kapcsolatos első dolgozata 1928-ban jelent meg. Az első rész1etesebb mű azonban csak 1944-ben született: John von Neumann és Oskar Morgenstern: Theory of Gamcs and Economic Behavior (Princeton University Press, Princeton, N. J.). A mű hatásosságára jellemző, hogy néhányan, közöttük A. H. Copeland így értékelte: “Az utókor ezt a művet a huszadik század első felének legfontosabb tudományos eredményei közé fogja sorolni”. (Bulletin of the American Mathematical Society, 51. kötet. 1945. 498—504. old.)

      A játékelmélet szellemében nagyon hasonlít a gravitáció elméletéhez, ugyanis mindkettő absztrakt modellből kiindulva közelíti meg a jelenségek széles körét. Az egyik elméletben az élőlények viselkedése meghatározott törvények szerint történik, hiszen a gravitáció elmélete lényegét tekintve pontosan megadja azt, hogy egy 12000 méter magasan, repülőgép, ejtőernyő és más eszköz nélkül tartózkodó pilóta hogyan fog mozogni. A játékelmélet pedig azzal foglalkozik, hogy bizonyos tervek esetében a lehetséges stratégiák közül kiválassza a legjobbat.

    7. Elfogult megjegyzések a módszerről
    8. Gyakran az embert körülvevő jelenségek borzasztóan bonyolultak ahhoz, hogy sikeresen elemezhessük őket. Az egyszerű elmélet megalkotásában ( egyszerűnek az olyan elméletet nevezzük, amelyben kevés axiómával és kevés szabállyal kell dolgozni és az egész többé-kevésbé kvantitatív ) segítséget nyújtottak az amatőrök, elsősorban a fizikatudósok

      A játékelmélet tudósait szükségképpen az emberek közötti bonyolult konfliktusok valódi teljessége körüli tudatlanságuk hajtja előre, mivel ha nem olyan tudatlanok, akkor majdnem biztosan megpróbálják elméletileg vizsgálni a problémákat. Hűségük ( talán inkább vakmerőségük ) abból az ismeretükből ered, hogy ők és módszereik az élettelen világban rendkívül eredményesnek bizonyultak. Ezeknek a tanulmányozását sem úgy kezdték, hogy egyszerre vizsgálták a mikroszkopikus részleteket és a dolgok egészet. A részletek sokaságával és rendezésének bonyolultságával nem tudtak megbirkózni. Mivel azonban ezen a területen némi sikereket már elértek, gyanították, hogy ez a módszer a komplikált esetekben sem mond csütörtököt.

      Az sem titok, hogy csak időnként érnek el sikereket, hogy tudásuk sokkal kevésbé teljes, mint az avatatlanok gondolják ( avatatlanoknak itt azokat nevezzük, akik úgy gondolják, hogy az élővilág sokkal bonyolultabb, mint az élettelen, mert az előbbi olyan jól van megalkotva. Például, a mai fizikusoknak az atom szerkezetének némely részérői csak homályos elképzelésük van ) mégis, az atombombát sikerrel le tudták írni. Az egyik fontos elemi részről, az elektronról bizonyos szempontból nagyon keveset tudnak, nem tudják például azt megállapítani, hogy egy adott időpillanatban hol van ( sőt azt is eldöntötték, hogy szigorú értelemben sohasem lehet választ adni erre a kérdésre). A matematikusoknak is vannak ehhez hasonló gyenge pontjaik. Például, évszázadokon át nem tudták eldönteni, hogy legkevesebb hány szín elegendő egy térkép kiszínezéséhez ( úgy, hogy a szomszédos országok nem lehetnek azonos színűek ), azt gyanítják, hogy négy szín elegendő, de eddig ezt nem tudták bebizonyítani.

      Az elmúlt száz évben a fizikatudósok fegyvertárában új fegyver jelent meg: a matematikai statisztika. Ezt régebben csak elvétve alkalmazták. Ma már egyre növekszik a jelentősége és az élővilág vizsgálatában is egyre fontosabb szerepet kap. A játékelméletnek is sok kapcsolata van vele. A statisztika alkalmazási lehetőségeit jól mutatja egy korai példa: a statisztika elmélete sikeresen írta le azt, hogy a Porosz Hadseregben a lórúgások következtében történt elhalálozások ( az évek során ) hogyan oszlanak meg:

      Az alábbi adatokra hivatkozok: Az amerikai hadsereg tíz hadtestében egy húszéves időszakban (1875—1894) hadtestenként és évenként a következő elhalálozási adatokat jegyezték fel:

       

      Halálozások száma

      Előfordulások száma

      Megfigyelés szerint

      Számítások szerint

      0

      109

      109

      1

      65

      66

      2

      22

      20

      3

      3

      4

      4

      1

      1

      A számítások szerinti érték meghatározása azon az információn alapult, hogy a halálos kimenetelű szerencsétlenségek átlaga kb. húszhónaponként egy. A számítások alapja olyan statisztikai elmélet, amely ritkán előforduló eseményekre különösen jól alkalmazható.

      Ha úgy vélnénk, hogy a lovak cselekedetei sokkal inkább megjósolhatók, mint az embereké, hát a módszer éppen ilyen sikeresen használható az emberi rúgások következtében elpusztult lovak számának becslésére. Annak, hogy a matematikai statisztika valóban alkalmazható az emberekkel kapcsolatban is, legjobb bizonyítéka a biztosítás. A biztosítási társaságok számadása ékesszóló bizonyítéka az elmélet sikerességének. Az emberekkel kapcsolatban is lehet becsléseket mondani.

      Milyen kilátásaink vannak a játékelmélettel kapcsolatban? Egyrészt az elmélet biztosan túl egyszerű ahhoz, hogy bármely katonai, közgazdasági és társadalmi problémát minden szempontból leírjon. Másrészt azonban elég ahhoz, hogy sok érdekes konfliktus lényeges elemeit megvilágítsa.

    9. Játékosok és személyek
    10. Most lássuk magát a játékelméletet. A pókerjátékkal kezdve vezessünk be néhány használt fontosabb fogalmat. A pókerjátékban egy csomag kártyával, némi pénzzel vagy más értékkel bizonyos szabályok szerint játszanak. A szabályok az összes cselekvési lehetőséget kimerítik: megadják, hogyan kell osztani, fogadni, mi történik a lapok leterítésekor, mi lesz az összegyűlt pénzzel.

      A póker ötszemélyes játék. Ez azonban inkább nyilvánvaló, mint igaz: lehet, hogy két játékos koalíciót alkot a játékban, megegyeznek egymás közt, hogy nyereményeiket és veszteségeiket közös alapba gyűjtik. Ha így van, akkor, ha a körülmények nem gátolják őket, közös érdekek alapján játszanak. Ha például a koalíció egyik tagja úgy látja, hogy a másik győzhet a lapjaival, akkor saját lapjaitól függetlenül tartani fogja a tétet. Ha már csak hárman játszanak, akkor lehet, hogy bedobja a lapot, hogy a harmadik csődbe jusson, lehet, hogy túllicitálja őt meg akkor is, ha saját lapjaival biztosan nem nyerhet. Röviden: a koalíció tagjai egyetlen, kétfejű személyhez hasonlíthatók.

      Ha két játékos koalíciót alkot, akkor nyilván nem ötszemélyesnek, hanem négyszemélyesnek érdemes tekinteni a játékot. Így arra a megállapításra jutunk, hogy nem az asztal körül ülő testeket, hanem az ellentétes érdekeltségű csoportokat kell megszámlálnunk. Elvünk szerint például a bridzs kétszemélyes játék, hiszen ha a partnerek nem cserélődnek, két különböző érdekeltségű csoport játssza. A “személy” és a “játékos” szavak az általunk használt értelemben épp úgy jelenthetnek jogi személyeket és szervezeteket, mint szokásosan értelmezett személyeket. Visszatérve a pókerre, azt olyan kétszemélyes játéknak is tekinthetjük, amelyben mi az egyik játékos, a többi négy személy a másik játékos. Azt is mondhatjuk, hogy a többiek által alkotott koalíció hírközlési szervei gyengék, vagy hogy valamilyen más hiba miatt bizonyul hatástalannak.

      A játékban részt vevő személyek száma a különböző érdekeltségű csoportok száma, a játékelmélet egyik alapvető fogalma. Az elemzés módja és az egész helyzet minden jellemvonása ettől a számtól függ. Szempontunkból három számnak van különös jelentősége: az egynek, a kettőnek és a kettőnél többnek.

      Egyszemélyes játék például a szórakozásból játszott pasziánsz. Még akkor is, ha a játékos a kártyalapokat 100 Ft-ért veszi valakitől és a nyerőhalmazba átvitt lapok darabjáért 500 Ft-t kap, ugyanaz a helyzet, csak a véletlen eseményeket kell megvizsgálni, a játékban nincs értelmes emberi ellenfél. A játékelmélet szempontjából az egyszemélyes játékok érdektelenek, és ezért valójában nem is foglalkozunk velük. Megoldásuk elvileg teljesen egyszerű: egyszerűen ki kell választani azt a cselekvéssorozatot, amelyik a legtöbb hasznot eredményezi. Ha véletlen események is szerepet játszanak a játékban, akkor a legnagyobb átlagos hasznot hozó cselekvéssorozatot kell választani. Bár ezzel igen sok gyakorlati probléma felett átsiklottunk.

      Az egyszemélyes játékok ( a pasziánszt kivéve ) mégis kétszemélyeseknek tekinthetők: olyan speciális kétszemélyes játékoknak, amelyekben az egyik játékos a játszó személy, a másik a természet. Ez a nézőpont akkor is hasznos, ha hiszünk abban, hogy a természet rosszakaratú lény és tönkre szeretne tenni minket. Például lehet, hogy a játékos nem tud eleget a természet “terveiről” és ezért nem tudja kiválasztani a legtöbb hasznot hozó cselekvéssorozatot. Vagy lehet, hogy a játékos ismeri a természet lehetséges “viselkedésmódjait” és tud valamit arról, hogy a természet milyen gyakorisággal alkalmazza azokat. Ebben az esetben a játékelmélet hasznos: bizonyos meghatározott, legkedvezőbb játékmódot ajánl.

      Az igazi kétszemélyes játék nagyon érdekes. A gyakorlatban ez fordul elő legtöbbször és ennek megoldásához jelenlegi fogalmi és technikai eszközeink elegendőek is. Hétköznapi értelemben ezeket tekintik konfliktusoknak. Van egy ellenfelünk, akiről fel kell tennünk, hogy intelligens és nyerni akar. Ha kedvezőnek látszó cselekvéssorozatot választunk, felfedezi tervünket, csapdába csal és így kamatoztatja felfedezését. Sok olyan játékot, amely valójában nem kétszemélyes játék, kétszemélyesnek fogunk tekinteni. Első ilyen példa a pókerjáték volt, ahol az egyik személy az egyik játékos, a másik az összes többi játékos által alkotott csoport volt. A játékelmélet eddig kidolgozott részei legfőképp a kétszemélyes játékokkal kapcsolatosak.

      Ha a különböző játékosok, azaz a különböző érdekeltségű csoportok száma kettőnél több, akkor minőségileg új dolgokat kell figyelembe vennünk. A legfontosabb újdonság az, hogy a játék folyamán változhat a különböző érdekeltségű csoportok összetétele, a játékosok ( ha ez számukra hasznosnak látszik ) időleges koalíciókat alakíthatnak és szüntethetnek meg. Például a pókerben előfordulhat, hogy egy erősen vezető játékos ellen a többi játékos koalíciót alkot, nehogy a vezető játékos mindenkinek ( még a viszonylag jól állóknak is ) elvigye a pénzét. A többszemélyes játékok jóval bonyolultabbak, mint a kétszemélyesek, megértésük is nehezebb. Ezért a továbbiakban csak kétszemélyes játékokkal foglalkozok.

    11. A nyereség
    12. A játékok osztályozásának és vizsgálatának egyik fontos szempontja tehát a játszó személyek száma; a “személy” szó itt különböző érdekeltségű csoportokat jelöl. A játékelmélet másik fontos fogalma a nyereség: mi történik a játék végén? Mondjuk a pókerjáték végén? A pókerben rendszerint kicserélődnek a vagyonok. Ha két játékos van, mondjuk a Kék és a Piros, akkor, ha a Kék 1000 Ft-t nyer, akkor a Piros 1000 Ft-t veszít.

      Másképp

      Kék nyereménye = Piros vesztesége,

      vagyis

      Kék nyereménye - Piros vesztesége = 0.

      Ha megállapodunk abban, hogy a nyereményt pozitív nyereségnek, a veszteséget negatív nyereségnek tekintjük, akkor ezt így is írhatjuk:

      Kék nyeresége + Piros nyeresége = 1000 Ft – 1000 Ft = 0.

      A nyereségek összege nem mindig nulla. Például, ha a nyertesnek a nyeremény 10 százalékával hozzá kell járulnia az elfogyasztott italokhoz és az egyéb váratlan kiadásokhoz (p1. a sarki rendőr megvesztegetéséhez), akkor a nyereségek összege nem nulla:

      Kék nyeresége + Piros nyeresége = 900 Ft - 1000 Ft = -100 Ft

      A fenti két eset alapján a játékokat két alapvető típusba sorolhatjuk aszerint, hogy a nyereményeket pozitív nyereségekként, a veszteségeket negatív nyereségekként számlálva a nyereségek összege nulla-e, vagy nem. Ha a nyereségek összege nulla, akkor nullaösszegű játékokról beszélünk. Ha nem nulla, akkor (a matematikusok nem valami találékonyak) nem nullaösszegű játékokról beszélünk. A megkülönböztetés fontossága nyilvánvaló: a nullaösszegű játékokban jó, tiszta, zárt rendszerrel kell foglalkoznunk. Úgy képzelhetjük, mintha a játékosok vagyonukkal együtt be lennének zárva egy szobába. Az ilyen játékok megoldását bizonyos erőfeszítések árán meghatározhatjuk. A nem nullaösszegű játékokban viszont megtalálhatjuk a nullaösszegű játékokkal kapcsolatos összes problémánkat, és további nehézségekkel is meg kell küzdenünk. Ezt a helyzetet csak úgy tudjuk leírni, hogy bevezetünk egy harmadik fiktív játékost nevezzük tudománynak, vagy sarki rendőrnek.

      Így

      Kék nyeresége = 900 Ft

      Piros nyeresége = - 1000 Ft

      A rendőr nyeresége = 100 Ft,

      és most már

      Kék nyeresége + Piros nyeresége + Rendőr nyeresége = 900 - 1000 + 100 = 0 Ft.

      Így háromszemélyes, nullaösszegű játékot kaptunk, amelyben a harmadik személy olyan, mint a malomkőből készült nyaklánc. De ne felejtsük el azt, hogy a háromszemélyes játékok elemzése a kétszemélyesekénél lényegesen nehezebb, hiszen a többféle lehetséges koalícióval is számolnunk kell. Így a nullaösszegű játékok, különösen a kétszemélyes nullaösszegű játékok elemzése lényegesen, könnyebb feladatot jelent, mint a nem nullaösszegűeké.

      A társasjátékok ( póker, sakk, amőba, halmák ) rendszerint nullaösszegű játékok. Sok más konfliktus is nullaösszegű játéknak tekinthető. A játékelmélet eddig kidolgozott részei főként az ilyen játékokra vonatkoznak. A nem nullaösszegű játékokkal kapcsolatban is születtek és születnek eredmények. A kétszemélyes játékok körében érdekes, de nehezen vizsgálható eset az, amikor a névértékben egyforma nyereségértékek hasznossága a játékosok számára nem egyforma. Ez a helyzet még a társasjátékokban is gyakran előfordul.

       

    13. Stratégiák
    14. Miként a “személy” szót is, a “stratégia” szót is a hétköznapitól némiképp eltérő értelemben használjuk a játékelméletben. A mindennapi értelemben használt “stratégia” szóba beleértjük azt is, hogy a terv ügyes és jó, a játékelméletben viszont csak azt kívánjuk, hogy a terv teljes legyen. Stratégiának az olyan teljes tervet nevezzük, amelyet az ellenfél semmiféleképpen sem tud keresztülhúzni. Akármit is választ ugyanis az ellenfél, az a mi lehetséges cselekvéssorozataink halmazával együtt éppen a stratégia leírásának részét képezi.

      Így a játékelmélet stratégia-fogalma két lényeges szempontból különbözik a szó hagyományos jelentésétől: a stratégiának teljesnek kell lennie, de teljesen rossz is lehet. Így például a pókerjátékban az összes lehetséges stratégia között az is benne van, amelyikben valakinek pikk Royal Flushot osztanak és bedobja a lapot. Ez nem túl okos stratégia de stratégia. Éppen így, a halmáknál is, ha visszalépünk, jobb lépési lehetőséget is nyerhetünk. Az elemzés szempontjából teljességgel megközelíthető játékokban az összes lehetőséget előre láthatjuk ( ha nem gyakorlatilag, akkor elvileg ) és így az összes lehetséges stratégiát fel tudjuk sorolni.

      Most mar előhozakodhatunk a játékok osztályozásának és tanulmányozásának egy újabb fontos szempontjával: a játékosok számára lehetséges stratégiák számával. Ha a játékosokat Kéknek és Pirosnak nevezzük és például Kéknek három, Pirosnak öt stratégiája van, akkor a játékot 3 x 5-ös játéknak nevezzük.

      Amikor a játékosok számáról beszéltünk, akkor bizonyos számoknak ( az egynek, a kettőnek és a kettőnél többnek ) különös jelentőseget tulajdonítottunk. Hasonlóképpen, a stratégiák számának is vannak kritikus értékei; itt két főcsoportot kell megkülönböztetnünk. Az első csoportba azok a játékok tartoznak, amelyekben a több stratégiával rendelkező játékos stratégiáinak száma is még véges szám. Ez azt jelenti, hogy a stratégiákat bizonyos meghatározott idő alatt össze tudjuk számlálni. A második főcsoportba azok a játékok tartoznak, amelyekben legalább az egyik játékos stratégiáinak száma végtelen.

      Az utóbbi típusú, un. végtelen játékoknak sok érdekes és hasznos alkalmazása van, elméletük azonban meglehetősen nehéz.

       

    15. A kritérium

A matematikai modellalkotás örök problémája (a fából készült modellekkel szemben) a kritériumbetegség. Milyen kritérium szerint kell vagy kellene megítélnünk a játék különböző kimeneteleit?

Nézzünk egy bevásárlási problémát. Tegyük fel, hogy valaki 1000 Ft-ért húst szeretne vásárolni. Milyen húst vegyen?

Különféle mellékfeltételei is lehetnek, vagy olyan kényszerítő körülményeket is tekintetbe kell vennie, mint az ízlés, az allergia és a tabuk (tiltott dolgok). Lehet, hogy minimális energiát akar a vásárlásba befektetni, és csak annyit mond az eladónak, hogy “Amikor felénk jár, hozzon 1000 Ft-ért felvágottat, mindegy, hogy milyet”.

Általánosságban, kritérium-betegségnek azt a problémát nevezzük, hogy eldöntsük, hogyan kell mérni és a mértek alapján milyen viselkedésmódot kell választani. A játékelmélet az első kérdéssel kapcsolatban semmit sem tud mondani, viszont a második kérdésre jól meghatározott, explicit választ ad.

A játékelmélet szerint arra a kérdésre, hogy hogyan kell az értelmes embereknek viselkedniük, ha egy adott nyereségmátrixot elfogadtak, meghatározott válasz van. Az, hogy az embereknek bizonyos meghatározott módon kell viselkedniük, nem vonatkozik azokra a kötelezettségekre, amelyeket a törvény és az etika ír elő. Inkább valamiféle matematikai morálra vagy legalábbis a takarékosság elvére vonatkozik, amelyek azt írják elő, hogy a játékosok fő célja az legyen, hogy ellentétes érdekek által vezérelt ellenfelüktől biztos úton a lehető legtöbbet nyerjék el. Modellünk szerint mi ezt fogjuk ésszerű viselkedésnek tekinteni. A matematikai modelleket ( a csizmákhoz hasonlóan ) használat előtt ki kell próbálni, hogy elég jól állnak-e. A háborús gyakorlatból azonban közismert, hogy csámpás csizmában is lehet jól célozni.

Nézzük meg, milyen következtetésekre juthatunk a nullaösszegű játékok fenti modelljének alapján. Emlékezzünk vissza arra, hogy a nullaösszegű játékok olyan zárt rendszerek, amelyekben a vagyonok cserelődnek, de a játékosok vagyonának összege állandó. Az elemzést egyszerűbbé teszi és semmit sem változtat meg, ha egy pillanatra feltesszük, hogy a játék mátrixának minden nyereségértéke pozitív. Ebben az esetben a stratégia Piros számára csak a lehető legkevesebb veszteséget biztosító stratégia kiválasztását jelenti. A játék nyilván igazságtalan Pirossal szemben, de hagyjuk, hadd adakozzék a köz javára.

A játékelmélet szerint tehát Kéknek olyan módon kell viselkednie, hogy Piros különböző viselkedésmódjai által meghatározott legkisebb nyereményei közül a legnagyobbat éri el. Piros pedig figyelembe véve Kék különböző viselkedésmódjait választja ki a legnagyobb, vagyis számára legkedvezőtlenebb nyereségeket eredményező viselkedésmódok közül a számára legkedvezőbbet, a legkisebb nyereséget. Ez a gondolkodásmód ( feltéve, hogy a játékosok következetesek ) elégséges alapot nyújt a stratégiák közti választásra. Ha Kék eltér az így meghatározott stratégiától, akkor lehet, hogy kevesebbet fog nyerni, mint amennyit nyerhetne. Ha Piros tér el ettől, akkor lehet, hogy többet kell fizetnie, mintha helyesen játszana.

A fenti okoskodás a játékelmélet alapvető következtetése. Minden kétszemélyes játéknak van olyan játszási módja, amely a fenti kritériumot kielégíti. Mégis, mint a húsvásárlási példában is, nem ez az egyetlen kritérium: ha például tudjuk ellenfelünkről, hogy tudatlan vagy hogy ostoba, akkor másképp kell stratégiát választanunk. A játékelmélet azonban a játékosok képességeinek vizsgálatával nem foglalkozik, úgy vizsgálja a problémákat, hogy a játékosokat egyformán intelligenseknek tekinti.

Kék és Piros céljai megfogalmazásunk szerint látszólag különbözőek, hiszen Kék esetében nyereményről, Piros esetében veszteségről beszéltünk. A különbség csak látszólagos, valójában a két játékos azonos módon gondolkozik. Csupán arról van szó, hogy megállapodásunk szerint a játék mátrixában a pozitív számok azt jelentik, hogy Kék nyer, a negatívok azt, hogy Piros nyer. Tehát Kék a maga számára maximális, Piros pedig Kék számára minimális nyereséget szeretne elérni. E számolástechnikai megállapodás mögött azonban Kék és Piros alapvetően szimmetrikus viselkedésmódja áll.

A program elkészítésének során ezeket az elméleteket fogom használni, de előtte rátérnék a táblás játékok sokféle világára.

  1. Táblás játékok
    1. A táblás játékok sokszínűsége
    2. A kétdimenzióban elhelyezett különféle rendezettségű és cella formájú játékok ősidők óta a legkedveltebb játékok közé tartoznak. Több ezer játék alakult ki az idők során, amely egyszerű kellékekkel ( táblák, bábuk, esetleg dobókockák ) és könnyen megtanulható szabályokkal bárki számára elsajátítható, kellemes szabadidő eltöltést biztosít. Ezek a játékok nagyban fejlesztik a logikai és stratégia képességeket, miközben szórakozást is nyújtnak. A matematikai és egyéb problémák megértése és tanítása is hatékonyabb egy-egy célratörő játékkal.

      A játékok sokszínűségét mutatja, hogy a táblákon kirajzolódó “pálya”, a játékosok száma, a lépések szabálya, a szükséges tudásszint annyira sokrétű, hogy ezek teljességgel való leírása is problémát okoz. Némelyik játék szabályai kisebb módosításokkal kerültek a köztudatba, mint ahogy a pókert is ahány ország, annyi szabályváltozata van.

      Ebben a fejezetben különböző módón osztályozva mutatok be táblás játékokat ( a teljesség igénye nélkül ), néhány szóban elemezve a programozási szempontokból.

    3. Játékosok száma
    4. Egyedül is lehet játszani, a képességeinket fejleszteni, de szinte mindenki jobban szereti, ha egy ellenféllel méri össze a tudását. Ez akár lehet hús-vér vagy virtuális intelligencia.

      A játékokat 3 fő csoportba érdemes sorolni a játékosok számát illetően:

      1. Egyszemélyes játékok
      2. Ilyenek például a szoliter játékok. A szoliter egyszemélyes feladványként lett kedvelt elmejáték. Legismertebb változatainak lényege, hogy egy meghatározott alakzatban a táblára felrakott bábukat, az ütési szabályok betartásával, egyetlen egy maradóig, mind leszedjük. Pontosabban: egy, a megoldást eredményező ütéssorrend megtalálása a feladat.

        A másik nagysikereknek örvendő egyszemélyes játék az aknakereső. A feladat egyszerű: meg kell keresni az összes ( gombok alá elrejtett ) aknát. A játékost segítik a bombákkal érintkező ( 8 vagy a széleken kevesebb minimum 3 ) cellákban elhelyezett számok, amelyek a környezetükben lévő aknákról árulkodnak. A játékos megítélése alapján fedi fel a gombokat törekedve arra, hogy ne aknát találjon. Ha aknát sejt alatta, akkor azt megjelöli. A játékos akkor nyer, ha az összes aknát megjelölte. Ilyenkor az “ellenfele” az elrejtett aknák és az idő. Létezik olyan elhelyezkedése az aknáknak, amit nem lehet triviálisan kiszámítani. Ilyenkor marad a véletlen kiválasztás vagy a bumm! L

        Programozási szempontból az egyszemélyes játékok nem okoznak nagy problémát, hiszen a program feladata a megjelenítés, a lépések megvalósítása és a szabályok betartása. Nem kell foglalkozni ellenlépésekkel és különböző stratégiákkal.

        A játékos akkor nyer, ha teljesíti a végállás feltételeit, amit a programozás során nem okoz túl bonyolult problémát. A játékok megnehezíthetőek időkorlátokkal, vagy az idő alapján adott pontokkal. Ettől versenyfeladattá válik a játék, hiszen nem csak az feladat teljesítése a fontos, hanem, hogy ki a gyorsabb.

        Ez így több játékos részére is közös játékot biztosít. A játszma egyjátékos részvételét igényli, de a játszmák számától függetlenül egy közös játékká válik egymás ellen, vagy saját magunk ellen.

      3. Kétszemélyes játékok
      4. Ez a leggyakoribb játékfelállás, amikor két ember egymás ellen mérkőzik. Nagyobb versenyeken, ahol több versenyző is részt vesz, össze tudják mérni a tudásukat, hiszen kettesével mérkőznek meg és a legjobbak maradnak bent a döntőben. A döntőig eljuthatunk kieső ágon ( Az összepárosított játékosok közül a nyerteseket párosítják újra össze és így addig folytatják, amíg el nem érik a döntőt. A vesztesek búcsúznak, vagy a dobogós helyezésért játszanak. Megeshet, hogy a “második tudású” versenyző az első körben kiesik a legjobb “segítségével”… ) vagy a másik módszer a körmérkőzés ( Ez egy kicsit igazságosabb módszer, hiszen mindenki játszik mindenkivel és a nyert játszmák alapján rangsorolják őket. Holtverseny esetén szétlövés következik ).

        A kétszemélyes játékok lényege, hogy egyenlő esélyekkel ( egyenlőtlen felállás esetén revans ) küzdenek egymás ellen. A két játékos közül csak az egyik nyerhet, a másik veszít, de mindkét játékos nyerni akar. Esetleges döntetlen esetén még egy játszma, vagy sorsolás dönti el a nyertest. ( kockadobás, pénzfeldobás )

        Tehát az emberi elmék stratégiai képességei mérik össze erejüket. A két játékos stratégiája lehet különböző is, hiszen sok játékban például a felállás és a szabályok is azonosak a résztvevőknek, de a lépést kezdő játékos mégis előnyben van, ezért a kezdő az előny megtartásával totális győzelmet akar elérni, míg a lépéshátrányban szereplő játékos megpróbálja átvenni a lépéselőnyt, de legalább döntetlent kiharcolni. Ilyenek például a sakk, a dáma, fonákollós játékok. A másik ok a stratégiák különbözőségére a szabályok vagy kezdőállások különbözőségéből adódik. Itt a játékosoknak másképpen helyezkednek el a bábúik vagy még a bábúk száma sem egyezik meg.

        Ilyenek például: “agarak és rókák”, “farkas és kutyák”, “várjáték”, “róka és libák”, “tablut”, “pókháló” játékok, ahol a az üldöző és üldözött bábuk száma nem azonos. A szabályok többségében az üldözők vannak többen.

        Programozási szempontból a kétszemélyes játékoknál kezdődik a nehézség. ( Ha a játék megjelenítése, a szabályok felügyelete csak a feladat, akkor az egyszemélyes játékokhoz hasonlóan ez nem okoz problémát, csak a játékosok sorrendjére kell figyelni. Azonban a játék végkimenetelét nem mindig könnyű meghatározni. ) Ma már igény, hogy az “intelligens” gépekkel mérjük össze stratégiai képességeinket. Hiszen manapság ritkán ér rá két ember néhány jó partira. Ennek az oka “felgyorsult világ”, az emberi érintkezés távolódása és egyéb szociális problémák, de ennek felderítése nem célja a dolgozatomnak. Ennek ellenére az évek során a játék ismét divatba jött. Régen ez többnyire a gyerekek kiváltsága volt, de ma már mindenki szeretne játszani a televíziós műsorokban a rádiós játékokban, az interneten…

        Tehát programoznunk kell egy okos ellenfelet aki legalább olyan jó mint játékos parnere, mert különben nem lenne élvezetes a játék. Érdekes pszichológiai tulajdonság, hogy ha az ellenfelünkkel szemben mindig nyerünk, vagy olyan erős, hogy hosszú gyakorlás után sem vagyunk képesek megverni, akkor elmegy a kedvünk az ellenfelünkkel való játéktól.

        A gép elleni játszmák alatt sokat lehet tanulni, taktikákat lehet ellesni kigondolni. Ezáltal sokat fejlődhet a stratégiai képességünk. Ez egy kis paradoxont is okozhat, ha a programunk jól játszik, de néha tudunk ellene nyerni, és gyakorolva már ügyes játékosokká válunk, de a program azonos szinten marad, akkor megunjuk a gyenge ellenfelet. Ha viszont erős az ellenfél, akkor meg elmegy a kedvünk a gyakorlástól. Erre a legegyszerűbb módszer, ha a gép a lépési stratégiáit nehézségi szintekre bontjuk, és mi határozhatjuk meg, hogy milyen erős legyen az ellenfél. ( az a megoldás is beválik, ha az gép néha-néha direkt hibázik )

      5. Többszemélyes játékok

      A többszemélyes játékok az “igazi” társasjátékok. A játék ilyenkor felszabadultabb, hiszen nem ketten mérik össze tudásukat és az egyik nyertes, a másik a vesztes, hanem többnyire egy nyertes van, a többiek pedig kollektívan a vesztesek. A másik oka, hogy ezek a játékok nagy részébe bele szól a szerencse, dobókocka, vagy egyéb eszköz dönti el a lépéslehetőséget. Pl.: “Ki nevet a végén?” játékban nagyobbrészt a szerencse játszik szerepet, mint a taktikázás. Az egyetlen általunk irányított dolog, hogy a dobásunk eredményét melyik bábúnkkal akarjuk lelépegetni. Ez nem ad túl nagy szabadságot a stratégia területén, de ezeknél a játékoknál nem is ez a lényeg, inkább a közös szórakozás kerül az előtérbe.

      Ezen játékok között is van olyan, amely nagyobb stratégiai készséget igényel. Ilyen a RISK hadi játék, ahol “háború” folyik a világtérképet imitáló pályán a különböző országokért. A játék alatt meglehet egymást támadni, szövetséget lehet kötni és a hadtesteinket átszállíthatjuk gyarmatainkra. Bár a játékot nem vesszük komolyan, és itt is szerepe van kis részben a szerencsének, de jó ellenfelekkel komoly stratégiákat lehet kieszelni.

    5. A résztvevő bábúk száma
    6. A rengeteg játékszabály alakult ki, ezekben szinte minden variációban fellelhető a bábúk száma és viszonya. A legtöbb játékban a résztvevő ellenséges bábúk száma megegyezik és egyenrangúak. De sok az olyan táblajáték, ahol az ellenfeleknek nem azonos számú és lépés szabályú bábúik vannak. Vannak játékok, ahol a bábúk száma változik ( leütik, beér …)

      1. Azonos számú és rangú bábúk

Minden résztvevőnek azonos számú bábúval kell a játékot végigjátszania. A játékszabályok és lépéslehetőségek minden játékosnak azonos feltételeket biztosítanak. Természetesen egy komoly előnyt figyelembe kell venni a játékok vizsgálata során, mégpedig, hogy előnyre vagy esetleg hátrányra tesz-e szert az aki az elsőként lép.

Kétféle képen folytatódhat a játék:

Ilyen játékok a “go” vagy a “malom”. A Go játékban összesen 180-181 bábút tehetünk fel a pálya rácspontjaira felváltva. A go-val ellentétben a malom játékban, ha leraktuk az összes 9 bábunkat, akkor már nem szaporítjuk tovább, hanem léphetünk a pályán lévőkkel. Érdekes, a növekedő bábúk szám után ismét csökkenni fog, hiszen az ellenfél “elfogyasztása” a cél.

    • vagy pedig szép lassan fogynak a bábúk a kezdő felállás után.

Ide sorolhatóak l: “sakk”, “dámák”, ahol az a cél, hogy az ellenfél bábúiból minél többet leüssünk, de mi minél kevesebb veszteséggel ússzuk meg.

      1. Eltérő erőviszonyok, előnyadós játékok

Ezek igen érdekes felállást alakítanak ki, mivel az ellenfeleknek nem azonos a babúik száma, sőt a lépés szabályaik sem azonosak. Ezért az ellenfelek stratégiája is különböző. Ezen játékok a változatosságukkal is érdekesebbé teszik a játszmákat. Néhány érdekes felállás:

Ezen játékok két játékot, stratégiát rejtenek magukban, ha az egyik oldalon már jól játszunk, akkor jöhet a helycsere.

      1. Változó bábú szám

Ezek szinte mind fonákollós rendszerű játékok, ahol lépésről lépésre változik az ellenfelek babúinak száma és erőviszonya. A bábú gyarapodást kétféle módon érhetjük el, az egyik módszer a “szaporodás” ( lerakunk egyet egy üres helyre érintve valamelyik már lerakott bábút ) vagy pedig hódítunk az ellenféltől. Általában ez a két módszer egyszerre alakul ki. Természetesen itt az a lényeg, hogy a pálya minél nagyobb területeit úraljuk, míg az ellenfelünknek kevés parcella jusson. Ezen játékok közül a legismertebb az “othello” ahol lépéskényszer a szaporodás és a hódítás is egyben.

 

    1. Jellegzetes lépések
    2. Ebben a fejezetben bemutatok néhány lépés fajtát, melyek jellemzőek játékok csoportjaira. Bár a lépés szabályok hasonlóak vagy azonosak ezekben a csoportokban, mégis a pálya mássága, vagy a szabályok alakítása jól elkülöníti a játékokat.

      1. Egyedi lépésszabályok

Vannak speciális, egyedi lépés szabályú táblás játékok, nincs sok belőlük, de van néhány módosított változatuk. Ilyenek:

      1. “Átugrálós”

A szoliterek, dámák, halmák és ehhez hasonló játékcsoportok egyik legáltalánosabb lépésszabálya. A lényege, hogy a pálya adottságaihoz mérten akkor léphetünk 4 vagy 6 irányba, ha át tudunk ugrani 2 távolságba lévő üres helyre, ha a kiindulás és a érkezési pozíció között található egy másik bábú.

Ilyenek: Várvédelem, Róka és kutyák, Róka és libák, Asztar, Leopárdok és gulya.

      1. “Szaporodás-hódítás”

Ez a lépésszabály okozza a táblán lévő bábúk legnagyobb változását. Talán nem is babúkról kellene írnom, hanem területbirtoklásról, hiszen itt a pálya egyes celláinak a tulajdonjogáért és hódításáért folyik a küzdelem. A lépések lényege,

A 2 variációban nem szaporodtunk, de ha az új cella szomszédságában van ellenséges terület, azt elhódítjuk magunknak. Ez vonatkozik az 1 variációra is.

Ezen lépések legismertebb képvizelői:

      1. “Malom” játékok

Ez egy egységes játékszabályra épülő, rengeteg variációban létező játékcsoport. A szabályok 4 részre bonthatóak:

Az veszít, akinek 2 bábúja maradt. Ez játsszák 24, 9 vagy ettől eltérő csomópontú sík esetleg térbeli pályán 12, 9,3 vagy speciális számú babúkkal. Változatok: kismalom, hazárd malom, tizenkettes-, hatos- hármas malom.

 

  1. A halma játék elkészítése JAVA nyelven
  2. Ebben a fejezetben bemutatom, hogy hogyan kezdtem a programozási feladatom a játék megismerésével, majd elemzésével, a program tervezésével és hogyan jutottam el a kész programig, miként teszteltem. Ez egy hosszú folyamat eredménye, mely során felmerülő problémák és megoldásuk lényegét tudom csak bemutatni. A játék kiválasztásánál törekedtem arra, hogy ne legyen túl bonyolult, de mégis minél jobban megmutassa a hasonló alkalmazásokban felmerülő problémákat. A megoldás során sok olyan elméletet és algoritmust alkalmaztam, mely a főiskolai képzés anyagaira épül (operáció kutatás, számítógépes grafika, algoritmusok és adatszerkezetek, mesterséges intelligencia…), de azon túl is használtam tudás anyagot, mely az elkészítés során elengedhetetlen volt. ( JAVA, PERL, Fly, stb. )

    1. A játék szabályai
    2. A Halmáknak két fő változata van, az egyiket négyzethálós táblán játsszák, a másikat pedig hatágú csillag alakú területen, mely átlói egyenlőszárú háromszögeket alkotnak (Ezt a változatait “kínai sakk” néven ismerik a halma háromszög-rácsozatú játékmezőn játszható, hatszemélyes alapváltozatát. Amikor a GO kezdett hódítani Európában, akkor egy élelmes német játékgyártó igyekezett a halmát is keleti játékként népszerűsíteni. Mint tapasztaljuk: sikeresen).

      A pályák méretétől függ a bábúk száma vagy a játékosok száma. A jobboldali elrendezésben (10x10-es játékmezőn 4x10 db koronggal) négyen, a baloldaliban akár hatan is versenyezhetnek, különböző színű korongjaikkal. Játszható kétszínű korongokkal is, akár mint páros játék, akár mint olyan hatszemélyes játék, amelyben 3-3 játékos alkot egy-egy csapatot. Megegyezés szerint: teljes helycsere, vagy egy bábucsoport gyorsabb áttelepítése a verseny célja.

      A halmák: "helycserélős", ütés nélküli, békés, ugyanakkor kombinatív játékok Lehet egyedül is játszani, mint feladványmegoldásként, de lehet 2, 4, akár 6-an is játszani.

      A társas halmajátékok célja: a tábla egyik sarkában felállított korongjainkat az átellenes sarokba (ellenfelünket megelőzve) áttelepíteni bábuinkat az ellenfél bábuinak helyére, vagy a nekünk kijelölt helyre.

      Lépésszabály: tetszés szerinti irányban - jobbra, balra, négyzethálós pályán (4): fel, le míg háromszögrácsos pályán (6): átlósan is - a szomszédos üres pozícióra léphetnek a korongok: egy lépésben, egy korong, egy pozícióval. Ugrani is szabad, mind a saját, mind az ellenfél korongját átugorhatja a "lépő-haladó korong" (de az ellenfél korongjait nem ütjük ki).

    3. A program tervek

Azt írják és tanítják a “nagy” könyvekben, hogy a programot először a struktogramm, folyamatábra megrajzolásával kell kezdeni. Bevallom eddig még nem láttam gyakorló programozót, aki így kezdte volna. J Ez egy kicsit olyan, mint a KRESZ vizsgán, aki nem 10 és 2 óránál fogja két kézzel a kormányt az megbukik, de az utcán nincs vezető aki ezt követné - csak néhány mazsola -. 16 éves programozási gyakorlatom alatt talán csak egyszer készítettem ilyet nem kötelező jelleggel. Szóval most sem prezentálok ilyet. De megfogalmazódtak igények a programmal szemben, amik megoldásra várnak majd. És kialakult egy lista, amely fontossági sorrendben megjelölte a program kritikus elemeit, amikkel érdemes néhány tesztet lefuttatni, mielőtt a többi elem ráépülne. A programmal szembeni elvárások:

Most nézzük a problémás dolgokat. JAVA-ban nincs beépített drag and drop lehetőség, sőt az elemek mozgatása, elhelyezése sem túl fejlett, ezért az első programsorokat a manók létrehozására, megfelelő mozgatás és elhelyezésre írtam. Ez nemcsak a felhasználók dolgát könnyíti meg, hanem a programírás-tesztelés fázist is komfortosabbá teszi. Miután elkészült a háromszögrácsos háttérképre pozicionáló és egérrel kezelhető drag and drop-os manók sora, jöhetett a pálya modellezése. Miután a pálya celláit feltöltöttem – üres, tiltott, manó elemekkel – már lépkedhetővé vált a játék. Két-három emberi játékos már játszhatott is rajta, bár még nem volt a lépésszabályt betartó ellenőrzés. Ha nincs, hát csináljunk. Így elkészült az a rutin is ami jó lépés esetén azt engedi lerakni, ellenkező esetben pedig visszadobja az eredeti helyére.

Mondhatnánk, hogy már kész is egy játék, de még se ellenfél, se tesztelés, amit a későbbi fejezetekben részletesebben bemutatok.

    1. A pálya kialakítása

Nem véletlenül választottam, a speciálisabb, nehezebb háromszöges pályát. A raszteres pályán az elemek megjelenítése, az adatok tárolása és lépések számítása sokkal könnyebb. A háromszögrácsos pálya lekezelése okoz némi nehézségeket. Mivel a programfejlesztő programok nincsenek felkészítve ilyen problémákra, ezért át kell alakítani a problémákat kezelhető formára. A feladat: a háromszögrácsos pálya átalakítása egy tároló formátumra, amely képes megmutatni a szabad, a foglalt és a tiltott mezőket, továbbá egyszerű számításokkal elvégezhetőek a lépések és ellenőrzésük.

Nos, nézzük néhány lehetőségek, amelyek megközelítik az előbb támasztott igényeket.

 

A gráftároló adatstruktúra részlete:

1

Üres

2

3

nil

nil

nil

nil

               

2

Üres

1

3

6

7

nil

nil

               

3

Üres

1

2

7

8

nil

nil

               

4

Zöld

5

11

nil

nil

nil

nil

               

5

Zöld

4

6

11

12

nil

nil

               

6

Zöld

2

5

7

12

13

nil

               

7

Üres

2

3

6

8

13

14

               

8

fehér

3

7

9

14

15

nil

Ezek a következő képen néznének ki az adattároláskor:

palya[1]=(0,2,3,0,0,0,0);

palya[2]=(0,1,3,6,7,0,0);

palya[3]=(0,1,2,7,8,0,0);

palya[4]=(3,5,11,0,0,0,0);

palya[5]=(3,4,6,11,12,0,0);

palya[6]=(3,2,5,7,12,13,0);

palya[7]=(0,2,3,6,8,13,14);

palya[8]=(2,3,7,9,14,15,0);

Ez az elmélet jól felépíthető adatstruktúrát képes létrehozni, de van vele pár probléma. Nevezetesen, nem lehet megállapítani triviálisan az ugrás irányhelyességét. Tehát, ha a 5-ös mezőről szeretnék lépni, a körülötte lévő mezőkről meg tudom állapítani, hogy nem üresek, tehát ugranom kell ( ha találok ugrótávolságban és irányban üres mezőt ). A szomszéd szomszédainak ürességét meg tudom állapítani, de azt, hogy helyes irányban ( azaz egyenes vonalban ) vannak-e azt sajnos nem. Ezen úgy lehetne segíteni, hogy a gráf csomópontjainak tároló vektorai az adatokat plusz tartalommal értelmezi. Pl. a szomszédok linkjeinek helye határozza meg az irányukat ( amit előre ledeklarálunk, és még adattöbbletet sem okoz ). Meglátásom szerint ez még így sem túl egyszerű. Keressünk jobbat! ( ha van )

Ez az átalakítás egyszerűnek és gazdaságosnak tűnik, de egy nagyon fontos információ torzulást okoz. Nem azok a szomszédai, mint az eredeti alakzatban. Ha az előző próbálkozás tesztjét itt is megpróbáljuk végrehajtani, akkor azt tapasztaljuk, hogy már az egyszerű lépésnél is gondunk van a szomszédok meghatározásában. Kik is a [1,2] cellában található zöld manó szomszédai. Gyakorlatban a [0,1], [1,1] tiltott cellák és a [0,2], [2,2], [0,3], [1,3] cellák, ahol a testvérei tanyáznak most.

Az x, y cella szomszédos mezőinek kiszámítása:

tabla[x-y mod 2,y-1]

tabla[x-y mod 2+1,y-1]

tabla[x-1,y-1]

tabla[x+1,y-1]

tabla[x-y mod 2,y+1]

tabla[x-y mod 2+1,y+1]

Amint látható nem egyszerű esettel állunk szemben hiszen minden sorban más az algoritmus, és ez még csak az egyszerű lépés keresésének kezdete lenne. Igen bonyolult lenne az ugrás és a sorozatos ugrás számítása és ellenőrzése.

Most kaptunk egy olyan vetületet, amelyre igaz, hogy bármely aktív cellának a szomszédos cellái azonos módon határozhatóak meg. Bár az így kapott mátrixunknak sok használatlan cellája keletkezett, de ez nem okoz gondot a tárolásban. Egy ekkora “pazarlás” nem okoz nagy tárnövekedést hiszen csak 6x9=54 byte vagy integer esetén ennek a négyszerese a többlet.

Van egy 13 x 9-s mátrixunk, amin meg kell határoznunk az aktív azaz használható cellákat és a passzív azaz tiltott ( pályán kívüli ) cellákat. Ezt a következő rutin végzi el úgy, hogy az aktív cellákba 0-t a passzívókba pedig 9-et tárol el. A 9 értékének nincs jelentősége csak ne legyen 0-üres 1..3 manók kódú. Én nem szeretem a –1 értéket használni. ( valószínűleg ASM előítélet J )

Két egymásba fordított háromszög kitöltési algoritmusát gondoltam ki a feltöltés egyszerűsítésére.

////a pálya szabad és tiltott helyeinek meghatározása

public void clear(){

int o=0;

for(int j=0;j<9;j++){

for(int i=0;i<7;i++){

o=0;

if(j<7){o=j+1;}

if((j>1) && (o<9-j)){o=9-j;}

if (i==0){tabla[0][j]=9;}

if((i<(7-o)/2) || (i>(5+o)/2)){

tabla[i*2+(j % 2)][j]=9;tabla[i*2+1+(j % 2)][j]=9;

}else{

tabla[i*2+(j % 2)][j]=0;tabla[i*2+1+(j % 2)][j]=9;

}

}

}

}

Ez a rutin használandó a kezdő inicializáláskor és az új menetek előtti tábla “tisztításához”. A kezdéshez már csak a manók elhelyezése a következő lépés.

    1. A manók elhelyezése
    2. A játék a képen látható alapfelállásból indul. Ehhez a különböző színű manókat a nekik megfelelő cellákra kell irányítani. Ennek a legegyszerűbb módja, ha sorban megadjuk a manóhoz tartozó cella koordinátákat és természetesen a tabla tömbben lefoglaljuk nekik a helyüket, hogy ne üres területként érzékeljük.

      A program ezen része igen egyszerű, csak a játékosok számára figyel, hogy 2 vagy 3 játékos manóit kell elhelyeznie a pályán. Ha csak ketten játszanak akkor a zöld manók nem kerülnek elhelyezésre. Itt állítja be a kezdo változó értékétől függően a kilep változót ( amit a beállítások ablakban lehet megadni ), azaz ki kezdi az első lépést. Ha a kezdo értéke nagyobb mint a beállított játékosok száma – user – akkor véletlen sorsolás történik.

      ////a kezdő poziciók feltőltése 2 ill. 3 játékos figyelembevételével

      public void kezdes(){

      if(user==3){

      mano[2][0].setPos(0,2);

      mano[2][1].setPos(2,2); //mano[2][1].setPos(6,2);

      mano[2][2].setPos(4,2);

      mano[2][3].setPos(1,3);

      mano[2][4].setPos(3,3);

      mano[2][5].setPos(2,4);

      for(int i=0;i<6;i++){mano[2][i].setVisible(true);}

      }else{

      for(int i=0;i<6;i++){mano[2][i].setVisible(false);}

      }

      mano[1][0].setPos(8,2);

      mano[1][1].setPos(10,2);

      mano[1][2].setPos(12,2);

      mano[1][3].setPos(9,3);

      mano[1][4].setPos(11,3);

      mano[1][5].setPos(10,4);

      mano[0][0].setPos(6,8);

      mano[0][1].setPos(5,7);

      mano[0][2].setPos(7,7);

      mano[0][3].setPos(4,6);

      mano[0][4].setPos(6,6);

      mano[0][5].setPos(8,6);

      //cél pozíciók

      endx[0]=6;endy[0]=0;

      endx[1]=0;endy[1]=6;

      endx[2]=12;endy[2]=6;

      //a kezdő játékos beállítása

      kilep=kezdo;if(kilep>=user){kilep=(int)(Math.random()*user);}

      lepesszam=0;ellep=0;

      }

       

    3. Lépések ellenőrzése

Ez az egyik legfontosabb dolog a programban. A feladat, egy olyan rutin megírása, ami leellenőrzi, hogy egy lépés megfelel-e a szabályainknak, vagy nem kivitelezhető. Ha csak játék felügyelet a célunk, akkor szinte csak ez az egy rutin játszik szerepet, hiszen a játékosok lépéskísérletét ez ellenőrzi és ha megfelel, akkor engedélyezi.

A rutinnak három adatot adok át: egy virtuális táblaállást, a kezdő- és a végpozíciót. A kezdő- és végpozíció megadása egyértelmű, hiszen azt ellenőrizzük, hogy egyikből a másikba el lehet-e jutni. A virtuális tábla pedig az ugrásoknál szükséges.

////egy lépés helyességének ellenőrzése (true, ha lehetséges)

static boolean ellenorzes(int[][] tablak,int x1,int y1,int x2,int y2){

Két problémát kell megoldanunk. Az egyik: hogy a pályán hogy modellezzük és értékeljük a lépéseket – mivel a pálya torzított volta miatt nem triviális a kezelése - , a másik: hogyan kezeljük a többlépéses, ugrásokat.

Nézzük először az egyszerű lépéseket. A háromszögrácsos pályán a piros helyről a zöld mezőkre lehet lépni, azaz az összes szomszédos mezőre. A jobb oldali ábrán látható a torzított megoldás, ahol sajnos nem a szomszédos cellák a lehetséges egyszerű lépések. Ezért az ellenőrzésben korrekciót kell végrehajtani.

Erre az egyik megoldás egy kis koordináta különbség számítás. Ha jól megnézzük az ábrát, észrevehető egy matematikai összefüggés: a x és y különbségek összege mindig 2, kivétel, ha x = 0 vagy, ha y = 2.

A másik megoldás: vegyük külön a felfele – átlósan – irányuló lépéseket a vízszintes lépésektől.

//egy lépés átlósan

if((Math.abs(x1-x2)==1) && (Math.abs(y1-y2)==1)){return true;}

//egy lépés jobbra vagy balra

if((Math.abs(x1-x2)==2) && (y1==y2)){return true;}

Tehát az egyszerű lépések ellenőrzésének megoldása készen van. Most nézzük, mi a helyzet az átugrásokkal. Először bemutatom az egyszeri átugrást, aminek az ellenőrzése az egyszerű lépésekéhez hasonló megoldáson alapszik, azaz megvizsgálja a lehetséges ugrási szomszédokkal egyezik-e meg a végpozíció.

 

 

Az ábrán ugyancsak a pirossal jelölt helyről a zöld mezőkre, cellákra juthatunk el, de a lépés engedélyezéséhez szükség van egy köztes mezőn elhelyezkedő foglaltságra, amit bármilyen színű manó megteszi. Ezt a program a következő képen számítja: mint az előbb két részre bontja az átlós és vízszintes ugrásokat. Az átlós ugrások x ill. y különbségének mindig 2-nek kell lennie – ez az ábra jobb oldalán jól látszik – és a köztük lévő cella tartalma nem lehet 0.

//1xi átugrás felfele vagy lefele, átlós ugrás

if((Math.abs(x1-x2)==2) && (Math.abs(y1-y2)==2) && (tablak[(x1+x2)/2][(y1+y2)/2]!=0)){return true;}

//1xi átugrás jobbra vagy balra

if((Math.abs(x1-x2)==4) && (y1==y2) && (tablak[(x1+x2)/2][y1]!=0)){return true;}

A pálya kialakítása miatt nem fordulhat elő az az eset, amikor a manónk egy tiltott területet ugrana át, hiszen ez sem 0 értékű.

Most jöjjön a neheze a “nem egyszerű lépések”, azaz a sorozatos ugrások. Ilyen esetekben a programozó először megpróbál modellezni, majd elemezni az keletkezett helyzeteket és ebből a jellegzetes, értékelhető eredményeket rendszerezni, majd megoldást kovácsol belőle.

Szabály:

A talált szabályosságok egy része nem használható fel, mert zsákutcába vezet. Ilyen pl. mivel minden ugrás x és y különbségének összege osztható 4, ezért ha ezt ellenőriznénk, akkor kiszűrhető lenne a rossz lépések egy része. De mi van a többivel? Nincs ellenőrizve a köztes manók megléte. Az ugrások iránya sem meghatározható az egyszeri ugrásokkal ellentétben.

Nézzük meg segítségként, hogy egy ember hogyan oldja meg ezt a feladatot. Először is megnézi, hogy a piros helyről melyik szomszédra lehet lépni. Ezeket az egyszerű lépéseknél már megoldottam. Utána nézi a nagyobb falatot azaz próbál ugrást végrehajtani. Tehát megnézi az előbb tárgyalt egyszerű ugrás esetét, van-e manó a szomszédos mezőkön és üres-e a mögötte lévő mező. Ha talál ilyet, akkor képzeletben odalép és ismét ugrási lehetőséget keres. No, de gyakran előfordul, hogy nem csak egy lehetőség van, ilyenkor úgynevezett “útelágazás” keletkezett, amit jó fejben tartania. Tovább ellenőrizve kiderül, hogy milyen messze megy az utoljára választott út. Most vissza kell menni az “útelágazáshoz” és megnézi, hogy a másik lehetőség milyen messze visz. A végén a legoptimálisabb utat választva gyakorlati lépésre szánja el magát.

Segít-e nekünk ez az elemzés? A válasz igen, hiszen mi is utat keresünk, bár már meglévő két pont között. Adott a kiindulási pont és a végpont. Azt kell eldönteni, hogy a kezdőpontból eljuthatunk-e a végpontba a pálya adottságaival. Az elemzést használva először egy koordinátavektorba – ebben tárolódnak az útelágazások - tegyük bele a kezdőpontunkat, majd keressünk az első koordinátavektorban lévő - kiinduló - mező körül egyszeri ugrásokat, ha találunk, akkor adjuk hozzá a koordinátavektorhoz, hiszen mindet ellenőrizzük majd, ha nem találunk ilyet, akkor ez egy végpont. Mindkét esetben le kell ellenőrizni, hogy a talált mezők megegyeznek-e a végponttal, ha igen akkor készen is vagyunk, hiszen el tudtunk jutni a kezdőből a végpontba. Ha az utolsó vektorkoordinátával is végeztünk, és nem találtuk a végpontot, akkor ez egy helytelen lépés ugrás szempontjából.

//nem egyszerű esetek

int[] helyx=new int[37];

int[] helyy=new int[37];

int pos=0,last=1;

helyx[0]=x1;helyy[0]=y1;

boolean jo=false;

int ox,oy;

while(pos<last){

ox=helyx[pos];

oy=helyy[pos];

for(int iy=-1;iy<2;iy++){

for(int ix=0;ix<2;ix++){

int nx=(ix*4-2);if (iy==0){nx*=2;}

int ny=iy*2;

nx=ox+nx;ny=oy+ny;

if ((nx>=0) && (nx<14) && (ny>=0) && (ny<9) && (tablak[nx][ny]==0) && (tablak[(ox+nx)/2][(oy+ny)/2] != 0)){

if ((nx==x2) && (ny==y2)){

jo=true;

}else{

boolean uj=true;

for(int i=0;i<last;i++){

if ((helyx[i]==nx) && (helyy[i]==ny)){uj=false;}

}

if (uj){

helyx[last]=nx;

helyy[last]=ny;

last++;

}

}

}

}

}

pos++;

}

return jo;

}

A rutin a helyx és helyy vektorban tárolja az ellenőrzendő útkereszteződéseket és csak akkor tárolja, ha még nincs a vektorban ez a koordináta. Ezzel elkerülhető a végtelen ciklus, redundancia. Az nx és ny az aktuálisan ellenőrzött ugrás cél cellájának koordinátái. A pos változó mutat a vizsgálandó útkeresztezés indexére, míg a last a vektorok számát mutatja.

    1. “Lépéstávolság”
    2. Az emberi intelligencia könnyen felismeri, a játékszabályokat követve, ahhoz hogy átérjünk a “túloldalra”, egy bizonyos irányba kell mozgatnunk a manóinkat. Érzékeljük, hogyha ebbe az irányba haladunk, akkor egyre “közelebb” jutunk a célhoz.

      Felvetődik a kérdés, hogy tudjuk meghatározni azt, hogy milyen távol vagyunk még a céltól? Hogyan mérjük ezt és mit tudunk kezdeni vele? “Lemérjük centivel?” azaz koordináta számítással kiszámítjuk a manók távolságát a ??? mitől is? Hiszen nem tudjuk melyik hova fog beérni. Kis csalással, de a lényegen nem változtatva vegyük az egyik célterületi pontot (praktikusnak tűnik mindegyik csoportnál a legtávolabbi pont) és ehhez határozzuk meg a távolságokat. Sajnos itt is eltévedhetünk, hiszen a manók nem egyenes vonalon haladnak a cél felé. Akkor próbáljuk meg felbontani x és y cellatávolságokra. Sajnos a pálya nem raszteres volta miatt (bár mi átalakítottuk, de ezzel torzítottunk rajta) a különböző manócsoportokra nem tudunk azonos és korrekt algoritmust írni.

      Ezek után kézenfekvőnek tűnik, hogy ha nem megy egyenes vonalban, nem megy raszteresen, akkor próbáljuk meg lépésvonalban meghatározni minden manóra. Ha belegondolunk ez azt jelenti, hogy hány lépés kell az aktuális manóknak ahhoz, hogy beérjen. Nos hova is? Ha rögzítünk egy pontot (legyen ez a már előbb említett legtávolabbi sarok) és összeadjuk a manók szükséges lépéseinek számát, amivel elérhetik ezt a célpontot, akkor kapunk egy “lépéstávolság” értéket, ami jól jellemzi és összehasonlíthatóvá teszi a manócsoportok erőviszonyait.

      Hogyan lehet ezt megoldani, hiszen egy torzított táblát használunk? Legjobb, ha lerajzoljuk. Az ábrán látható, a pálya, a manónk és a számára elérhető pozíciók. Természetesen csak a fehér területekre tud lépni, ha nincsenek letiltva. A cellákban látható számok azt az értéket mutatják, hogy a manónak hány lépésre van szüksége a számozott cella eléréséhez. A távolságok nem túl triviális képet mutatnak, de geometriai jellegzetességük miatt matematikailag megfoghatóak. Nem érdemes kódtáblát készíteni.

      A problémát az okozza, hogy függőleges irányba haladva a lépéstávolság megegyezik az y különbség értékével, viszont vízszintes irányban haladva a lépéstávolság pont kétszerese az x különbségnek. Ha a manó körüli pályát diagonális irányban felbontjuk, akkor láthatjuk, hogy a diagonál által letakart cellák és a felső és alsó negyedben a lépéstávolság megegyezik az y különbséggel függetlenül az x különbségtől. Ha viszont a maradék területet nézzük, arra jöttem rá, hogy az így elérhető cella az x és y különbség összegének fele.

      Tehát, ha a célterület a diagonálon van, vagy az fölötti-alatti negyed, akkor a

      távi = abs( ymanó - ycél )

      egyébként:

      távi = abs( xmanó - xcél ) + abs( ymanó – ycél ) / 2

      //a virtuális táblán milyen közel vannak a 'szam' manoi a célhoz?

      public int reltav(int[][] tablak,int szam){

      int tav=0,xx,yy;

      for(int j=0;j<9;j++){

      for(int i=0;i<14;i++){

      if(tablak[i][j]==szam+1){

      //kivonom az aktuális helyzetből a célpozició koordinátáit -> távolság

      xx=Math.abs(i-endx[szam]);

      yy=Math.abs(j-endy[szam]);

      if (xx>yy){tav+=(xx+yy)/2;}else{tav+=yy;} }

      }

      }

      return tav;

      }

      Egy kicsit előre gondolkodva az ehhez szükséges rutint paraméteresen oldottam meg, hogy bármilyen felállású tábla és manócsoport lépéstávolsága kiszámítható legyen.

      Ezt az elvet, rutint később jól feltudjuk használni.

    3. Beértünk-e?

A feladat számunkra, embereknek igen egyszerűnek tűnik. Ha az “ellenkező oldalra” érve mind a 6 manó a “sarokban” van, akkor beértünk a célterületre. Vajon hogy mondjuk meg a számítógépnek, hogy túloldal és azt hogy célterület?

Erre három lehetőség kínálkozik:

//beért-e a 'szam' mano csapata?

static boolean beert(int szam){

for(int i=0;i<6;i++){

if (Math.abs(mano[szam][i].tx-endx[szam])+

Math.abs(mano[szam][i].ty-endy[szam])>4

|| (Math.abs(mano[szam][i].ty-endy[szam])>2))

{return false;}

}

return true;

}

A rutin leellenőrzi az aktuális (szam) 6 manót és ha a célterületen van mind, akkor true értékkel tér vissza, ellenkező esetben false.

    1. Ellenfél, a stratégia
    2. Programozói szemmel ez a legnehezebb része a játékprogramoknak. ( a grafikát és a designt a megfelelő szakemberekre hagyva ) Mondhatnám úgy is, hogy ez a lelke a játéknak. Hiszen egyfajta szellemi vetélkedésre, küzdelemre készteti a humanoid ellenfelét. Ellenfél készítése igen magas szintű jártasságot igényel a játékban. Ezen felül komoly elemzési képességek is elengedhetetlenek. Természetesen ez a szint játékoktól függően igen tág skálán mozog, hiszen nem a azonos tudásszintet követel egy tick-tack-toe nevezetű játék vagy a sakk ellenfél algoritmus elkészítése.

       

      1. Mi az elérendő cél?
      2. Ezzel a játékprogrammal jól demonstrálható egy ellenfél stratégia kialakítása, hiszen lépésről lépésre változnak a lehetőségek, erőviszonyok. A lépéskombinációk nem bonyolultak, de nem alkalmazható előre legyártott lépéssor. Valódi küzdelem folyik, bár nincs leütés a játékban. A cél a saját és az ellenfél manóinak felhasználásával a leggyorsabban a túloldalra jutni és ott elfoglalni a helyünket. Azaz minél kissebre csökkenteni az előzőekben tárgyalt távolságot.

      3. Memória vagy gondolkodás?
      4. Felvetődik a kérdés, hogy milyen stratégiát válasszunk. Képezzük az összes lehetséges lépéskombinációt és abból szűrjük le a biztos nyerő, vagy előnyt megtartó lépéseket, vagy minden lépésnél használjunk egy optimális lépést kiszámító algoritmust?

        Nézzük az első felvetést. Ha le akarnánk tárolni az összes lépéslehetőséget és az ezekhez tartozó “nyerési” indexet mondjuk egy fastruktúrában, akkor mekkora tárolóra is lenne szükségünk? Egy pályaállás eltárolására minimum 7x9=63 byte szükséges. 3x6=18 manó van a pályán ami 37 mezőből áll. Egyszerűség kedvéért színfüggetlen manókkal számolok ( ennél több lenne, ha megkülönböztetném őket ).

        lépésállás, amit még meg kell szoroznunk a mátrix méretével azaz 63 byte-al. Tehát több mint 1.113.375.872.700 byte ami 1 Terrabyte fölötti érték. Ez a tárolóhely igény mintha sok lenne egy játéknak.

        Lehet-e egyszerűsíteni? Ha kiszedjük azokat a pályaállásokat, amelyek számunkra nem optimálisak ( ezek olyan állások, amelyek kívül esnek egy paralerogramma által meghatározható előnyös útvonalon ). Ezzel se sok memóriát nyerünk, viszont a lebutított program nem tudna mit kezdeni az előbb elvetett állások esetén. Konklúzió: a lépéstárolás értelmetlenné válik a nagy variációk száma miatt.

        Mi lenne, ha megkeresnénk ( feltéve hogy létezik ilyen ) a nyerő és legoptimálisabb lépésállásokat, kombinációkat és csak ésszerű mennyiséget kiszelektálva tárolnánk el. A program megpróbálná ráilleszteni a tárolt lépéseket az épen aktuális állásokra és ha egyeznek akkor annak megfelelően lépi a következő kombinációt, ellenkező esetben pedig megpróbál egy optimális lépést találni valamilyen algoritmus alapján, akár úgy, hogy közelebb kerüljön egy tárolt álláshoz.

        Ezzel az elmélettel az a gond, ami az előbb is problémát okozott, hogy a nyerő vagy optimális állasok, kombinációk megkereséséhez iszonyatos memória igény szükséges. Bár, ha már elkészült egy ilyen lépéskombinációs táblázatlista, annak már nincs túl nagy mérete.

        Ha egy ilyen játék pár száz kilobyte feletti mérettel rendelkezik, akkor nehézkessé válik a használata, főleg internetes közegben.

        Mi lenne, ha ezeket a lépéskombinációkat nem tárolt táblázatok alapján, hanem manó-lépéspozíció párokkal fastruktúrában kezelve, mélységi útkereséssel játszanánk végig az összes lehetőséget, és csak a nyerő lépéssorozatokat tárolnánk el. Nos ez a megoldás sem túl kecsegtető, hiszen az előbbi riasztó számok itt is megjelennek műveleti számban is.

        Lehet-e realtime algoritmust használni? Úgy tűnik nem maradt más hátra csak ez a lehetőség. Muszáj valamilyen egyszerű vagy bonyolult valósidejű, az aktuális állást elemző és ez alapján lépő algoritmus megalkotása. “Tanítsuk meg gondolkodni a programot!”

      5. Lépéslehetőségek keresése, mohó algoritmus

Ilyen esetekben kézenfekvő próbálkozás egy mohó algoritmus. Optimalizálási probléma megoldására szolgáló algoritmus gyakran olyan lépések sorozatából áll, ahol minden lépésben adott halmazból választhatunk. Sok optimalizálási probléma esetén a dinamikus programozási megoldás sok esetet vizsgál annak érdekében, hogy az optimális választást meghatározza. Ennél egyszerűbb, hatékonyabb algoritmus is létezik. A mohó algoritmus mindig az adott lépésben optimálisnak látszó döntést hozza. Vagyis, a lokális optimumot választja abban a reményben, hogy ez globális optimumhoz fog majd vezetni.

Mohó algoritmus nem mindig ad optimális megoldást, azonban sok probléma megoldható vele. A mohó stratégia egy igen hatékony eszköz, amely problémák széles körére alkalmazható.

Nézzük mibe is kapaszkodhat a mohó algoritmusom.

Adott a pálya pillanatnyi állása és 6 manó melyek közül az egyikkel kell majd lépnünk. Van egy lépésellenőrző és egy lépéstávolság számító rutin. Adott a cél is: beérni azaz minél jobban lecsökkenteni az adott színű manók összesített lépéstávolságát a lépésszabályok betartásával. Próbáljunk meg minden körben minél nagyobb, minél mohóbb lépéseket keresni az adott irány felé.

Erre találtam ki egy algoritmust, amely minden azonos színű manónak ( 6 db ) megnézi, hogy a pálya összes szabad mezője közül melyikre léphet, ugorhat. Ezekhez a lépésekhez kiszámítja a lehetséges lépéstávolságot, nyereséget, majd a legkedvezőbbet választja ezek közül. A választott lépést realizálva kialakul az új felállás.

Nézzük hogy is működik az algoritmus. Az átadott paraméterek: tablak a tábla aktuális állása, kordin és szam, a szam kódú ( színű ) manók koordinátái.

////egy gépi lépés számítása

public int[] gepi(int[][] tablak,int[] kordin,int szam){

int[] vec=new int[10];

int sx,sy,rt,tav;

int[][] t=new int[14][9];

int[] csx=new int[100],csy=new int[100],

int[] cex=new int[100],cey=new int[100],cm=new int[100];

//manok 0-5 koordinátái egyesével sx,sy-ba

for(int m=0;m<6;m++){

sx=kordin[m*2];

sy=kordin[m*2+1];

//az összes szabad és lehetséges lépés tárolása csx[],csy[] start; //cex[],cey[] end; cm[] mano sorszám

Most végigpásztázza a tablak összes celláját és ha talál üres cellát, akkor leellenőrzi, hogy oda lehet-e lépni az ellenorzes rutinnal. Ha a lépés megengedett, akkor eltárolja kezdő és vég koordinátákat és azt, hogy melyik manóról is van szó.

for(int j=0;j<9;j++){

for(int i=0;i<13;i++){

//ha találunk üres helyet, ahova léphetünk is

if ((tablak[i][j]==0) && (ellenorzes(tablak,sx,sy,i,j))){

//kezdő koordináta (honnan ugrik)

csx[cp]=sx;csy[cp]=sy;

//vég koordináta (hova ugrik)

cex[cp]=i;cey[cp]=j;

//kiröl is van szó?

cm[cp]=m;

cp++;

}

}

}

}

A tav változó fogja tárolni a legoptimálisabb lépés távolságértékét, ezért kezdőértéknek az aktuális állás értékét kapja ( ennél csak jobbat találhatunk )

//tav alapértelmezett értéke a mostani állás

tav=reltav(tablak,szam);

Az eltárolt koordinátapárokat rendre előveszi, felrakja egy t táblára és összehasonlítja az összes lépéstávolságát, ebből kell kiválasztani a legkisebbet.

//válasszuk ki a lépéslehetőségek közül egy optimumot

for(int i=0;i<cp;i++){

//t lesz a virtuális táblánk

for(int jj=0;jj<9;jj++){

for(int ii=0;ii<14;ii++){

t[ii][jj]=tablak[ii][jj];

}

}

//Ennek az lehetőségnek a távja

rt=reltav(t,szam);

//Ha kisebb akkor tároljuk el

if(rt<tav){

tav=rt;vec[0]=rt;vec[1]=cm[i];vec[2]=cex[i];vec[3]=cey[i];

}

* ide majd beszúrunk még néhány sort.

}

return vec;

}

Nos kész a “gondolkodó” ellenfél, aki már tud játszani, de természetesen nem túl okos még. A mellékletekben található első ábrán nyomonkövethető, ahogy a program keresi a legjobb megoldást a piros manók kezdőlépéseként. Először a “0”-ás sorszámú (legalsó) manóval próbálkozik, de neki nincs hova lépnie vagy ugrania. Próbálkozik tovább az “1” sorszámúval, mely 2 helyre ugorhat, ugyan ez a helyzet a “3”-al is. Így tovább keresi a lépéseket, de a “2” és “3”-as manó 4 ugrása a legoptimálisabb. Ezek közül kell most választani. A következő körben ugyan így keresi a jó lépéseket.

Tesztelve a program mohó stratégiáját ( ami így elsőre nagyon jól sikerült ) két fontos kivetni valót találtam.

Ne válasszuk az optimumot, hanem valamely környezetét? Sajnos akkor a stratégia csorbul.

Válasszunk másik optimumot? Hány optimumunk is van?

Tesztelve a játékot, általában egy lépéshelyzetben 2-4 azonos maximális nyereségű lépés is adódott. Kézenfekvő a megoldás, ha van több optimum, akkor ezek közül válasszunk egyet. De melyiket? Legyen véletlen kiválasztású, tehát nem lehet majd előre megmondani, hogy mit fog lépni a gép. Ezt a problémát oldja meg a következő sorok beillesztése a fenti csillaggal jelzett sorba.

if ((rt==tav) && Math.random()*10<3)){

tav=rt;vec[0]=rt;vec[1]=cm[i];vec[2]=cex[i];vec[3]=cey[i];

}

Ezt egy olyan megoldás lenne képes megvalósítani, amely a következő képen működik: keres egy lépéslehetőséget, majd az új felálláshoz keres egy újabb lépést és így tovább. Az megadott “előre gondolkodási szint” elérése után összehasonlítja az elért eredményeket és ki tud választani egy legjobb eredményt.

A megfogalmazás magába rejti a rekurzió szükségességét.

      1. Rekurzív algoritmus

Tehát a feladat egy olyan rekurzív algoritmus elkészítése, amely a megadott szintig ( rekurziós ismétlésszámig ) kiszámolja a lépések összes variációját és ebből meglépi a legkedvezőbbet.

Nagyszámú tesztelés és lépéselemzés után kialakult néhány elmélet a hatékonyabb nyerés érdekében. A következő ábrán bemutatok egy állást, amely jól szemlélteti, a mohó és egy rekurzív stratégia lépését. A egyszerűsített felállás távolságértéke: 10. Ezt kellene minél jobban egy jó lépéskombinációval lecsökkenteni.

A mohó algoritmus egyesével megkeresi a most pályán lévő 4 manó összes lépéslehetőségét. Ezek közül a legoptimálisabbnak ( lokális optimumnak ) a “2” manó piros nyíllal jelölt lépését választja, hiszen ez a többivel szemben ( mind maximum 1 lépést haladhatna a cél felé ) 2 lépéssel csökkentheti a lépéstávolságot. Ez az aktuális állásból a legnagyobb nyereséget kihozó lépés 1 körön belül.

Most nézzük mit mond a rekurzív mohó algoritmus erre az esetre. Nézzük 2 lépéses rekurzióra. Leképezi mind a négy manó lépéslehetőségeit, majd ezekhez keres újabb lépéslehetőségeket és a két lépés nyereségét összeadva a nagyobb értékűt választja ki. Ebben az esetben furcsa mód a kétlépéses optimum egy olyan lépéskombináció, amelynek első lépése negatív nyereségű azaz visszafele kell lépni ( ahogy az ábrán is mutatja a “2” manó narancssárga nyila ) ahhoz, hogy utána egy jó nagyot ugorhasson az “1” manó. Az így elért kétkörös lépésszám csökkenés 5. Ez sokkal kedvezőbb mint az egyszerű mohó algoritmus kétkörös maximuma: 3.

Nos akkor ezek után lássuk a rekurzív útkeresés megoldását:

Először is egy kis procedúra, amely a rekurzív rutinnak előkészíti a paramétereket és a visszakapott lépésjavaslatot meglépi. Itt a beállított rekurziós ismétlésszám 2 azaz két lépést gondolkodik előre.

////egy optimális lépés kiszámítása (a gép részére vagy géplép gombra)

public void geplep(int szam){

int[] k=new int[12];

int[] vec=new int[10];

for(int ii=0;ii<6;ii++){

k[ii*2]=mano[szam][ii].tx;

k[ii*2+1]=mano[szam][ii].ty;

}

//nehézségi szint

int szintem=szint[kilep];

if (szintem==0){szintem=2;};

//rekurziv meghívás

vec=gepi(tabla,k,szam,szintem);

//visszatérés M(vec[1]) sorszámú mano új koordinátái: X(vec[2]), //Y(vec[3]). új távolság (vec[0])

X=vec[2];Y=vec[3];M=vec[1];

//mano áthelyezése

mano[szam][M].setPos(X,Y);

//következő játékos

kilep=(++kilep)%user;

if(kilep<ellep){lepesszam++;}ellep=kilep;

//ezzel a lépéssel beért -> vége

if (beert(szam)){vege(szam);}

}

Ezek után az előző fejezetben leírt rutint átalakítottam rekurzívvá:

////egy gépi lépés számítása (rekurziv függvény)

public int[] gepi(int[][] tablak,int[] kordin,int szam,int rec){

int[] vec=new int[10],v=new int[10];

int sx,sy,tav,cp=0;

boolean first=true;

int[][] t=new int[14][9];

int[] k=new int[12];

//max 19 vektor

int[] csx=new int[100],csy=new int[100],cex=new int[100]é

int[] cey=new int[100],cm=new int[100];

int rt1,rt2,rt3;

//utolsó visszatérő érték a rel.táv.

if (rec==0){

vec[0]=reltav(tablak,szam);

return vec;

}

//rekurzió fokszámának csökkentése

rec--;

for(int m=0;m<6;m++){

//manok 0-5 koordinátái egyesével sx,sy-ba

sx=kordin[m*2];

sy=kordin[m*2+1];

//az összes szabad és lehetséges lépés tárolása csx[],csy[] start; //cex[],cey[] end; cm[] mano sorszám

for(int j=0;j<9;j++){

for(int i=0;i<14;i++){

if ((tablak[i][j]==0) && (ellenorzes(tablak,sx,sy,i,j))){

//kezdő koordináta (honnan ugrik)

csx[cp]=sx;csy[cp]=sy;

//vég koordináta (hova ugrik)

cex[cp]=i;cey[cp]=j;

//kiröl is van szó?

cm[cp]=m;

cp++;

}

}

}

}

//tav alapértelmezett értéke a mostani állás

tav=reltav(tablak,szam);

//ha tav=8 -> beértem

if(tav==8){

v[0]=0;return v;

}

//rt alapértéke 40, mert a kezdő állás távolsága 40

int rt=40;

//a lehetséges lépések analizálása

for(int i=0;i<cp;i++){

//t lesz a virtuális táblánk

for(int jj=0;jj<9;jj++){

for(int ii=0;ii<14;ii++){

t[ii][jj]=tablak[ii][jj];

}

}

rt2=reltav(t,szam);

//minden mano koordináta k-ba plusz egy lehetséges lépés átrakása

for(int ii=0;ii<6;ii++){

if((kordin[ii*2] == csx[i]) && (kordin[ii*2+1] == csy[i])){

k[ii*2]=cex[i];k[ii*2+1]=cey[i];

}else{

k[ii*2]=kordin[ii*2];k[ii*2+1]=kordin[ii*2+1];

}

}

//a lehetséges lépés elhelyezése a táblán

t[csx[i]][csy[i]]=0;

t[cex[i]][cey[i]]=szam+1;

//ennek a variációnak mekkora a távja

rt=reltav(tablak,szam);

rt1=reltav(t,szam);

//kovetkező rekurzív hívás

v=gepi(t,k,szam,rec);

rt3=v[0];

if(rt>v[0]){rt=v[0];}

if(rt<tav){

tav=rt;vec[0]=rt;vec[1]=cm[i];vec[2]=cex[i];vec[3]=cey[i];

}

if ((rt==tav) && (Math.random()*10<13)){

tav=rt;vec[0]=rt;vec[1]=cm[i];vec[2]=cex[i];vec[3]=cey[i];

}

return vec;

}

}

Ez a rutin már képes több lépést előre kiszámítani, de mivel az ellenfél is beleszól pályaállásba, ezért nem lehet előre lejátszani az egész játszmát. Meg kell keresni azt az optimális rekurziós ismétlést ami még hatékony stratégiát biztosít. Ennek érdekében tesztsorozatokat futtattam le gép-gép ellen 10000 menetig különböző szintekkel. Két információra voltam kíváncsi: tényleg hatékonyabb-e a rekurzív ismétlés és hány lépéskombinációt érdemes kiszámítani. Az eredmény igen érdekes:

Tehát a legoptimálisabb stratégiát a 2 lépéses rekurzió biztosítja ekkora pályán. Feltételezem, hogy nagyobb pályán esetleg több manóval hatékonnyá válhat a 3 lépéses rekurzió is, hiszen a körönkénti pályaváltozás jóval kisebb arányú lesz.

A kétlépésű rekuziós lépésfolyamatot részletesebben és szemléltetve a 3.11-es fejezet vége mutatja be.

      1. További lehetőségek?
      2. Elemezve tovább az emberi gondolkodás stratégiáját, még lehet trükköket beépíteni az ellenfelet szimuláló programba. Most a program a saját előrejutását keresi minél hatékonyabb módon. Egy emberi ellenfél nemcsak ilyen pozitív gondolkodást képvisel, hanem a lépéseibe belekalkulálja azt is, hogyha teheti akadályozza az ellenfelét. Tehát továbbfejlesztve a programot, azt is meg lehet oldani, hogy nem csak a saját manóknak keresi a legjobb lépést, hanem az ellenfél válaszreakcióit is kiszámítja és ezek után választja ki a számára legkedvezőbb, de az ellenfél részére minél kedvezőtlenebb lépést.

        Rá lehet hangolni a programot, hogy megkülönböztesse a biztos és a bizonytalan lépéskombinációkat, hiszen vannak olyan ugrássorok, amelyek több útvonalon is elérhetők, tehát az ellenfél lépése után is nagyobb eséllyel léphetők meg.

      3. Tesztelés

Ahogy már az elő fejezetekben is említettem, sok tesztelésre van szűkség a program formálása során, hiszen nem lehet előre meghatározni, papíron kiszámolni minden fontos elvet. A tesztelés sokat segít a program megalkotása során gyorsabb és hatékonyabb rutinok elkészítésében és az elkészült programrészek helyességének ellenőrzésében. Ezért külön programrészeket kell készíteni, amik a kész program szempontjából hulladékokká válnak, hiszen akkor már nem lesz rájuk szükség. A leghatékonyabb tesztellenőrzés a programsorok közé beszúrt adatkiíró sorok. Az írás során ezek a kiírások mint a nápolyiban a csokirétegek választják el a lényeges blokkokat. Ezenkívül néhány csak tesztelésre alkalmas rutint is el kell készíteni, ilyen például egy tábla tartalom kiíratás, ami arra szolgál, hogy az aktuális állás tároló tartalmát megjelenítse a JAVA-konzolon.

////aktuális pályaállás kiíratása konzolra

public void tomb(int[][] tablak){

System.out.println();

for(int j=0;j<9;j++){

for(int i=0;i<14;i++){

if (tablak[i][j]<5){

System.out.print(""+tablak[i][j]);

}else{

System.out.print(" ");

}

}

System.out.println();

}

}

Készültek még olyan kiegészítő átalakítások is, amelyekkel lépésről lépésre regisztrálták a program javaslatait, majd ezeket kinyomtatva elemeztem papíron, a hibát lokalizálva javítottam. Ezeket a részeket felhasználva készült egy lépésfolyamat adatbázis, amelyhez egy külön PERL programot írtam, ami elkészítette a vizsgadolgozat végén található lépéseket illusztráló képsorokat. Ez a linuxon futó script az adatok alapján egyesével megrajzolta a program által keresett lépéslehetőségeket, majd összesítette egy gif képen.

A programot képessé tettem, hogy beavatkozás nélkül folyamatosan lépkedve játszmákat futtasson, és az összegzett eredményeket kiírja. A paramétereket változtatva készítettem 10000 játszmás teszteket, amik a nyerési arányt és lépésátlagot szolgáltatták. Néhány érdekes tesztadat:

Lépéskör

Piros 1 szint

Fehér 1 szint

Zöld 1 szint

Nyerő lépések

10000

3336

3372

3292

14,7

Lépéskör

Piros 2 szint

Fehér 2 szint

Zöld 2 szint

Nyerő lépések

10000

3392

3313

3295

12,3

Lépéskör

Piros 3 szint

Fehér 3 szint

Zöld 3 szint

Nyerő lépések

10000

3656

3341

3003

14,9

Lépéskör

Piros 1 szint

Fehér 2 szint

Zöld 3 szint

Nyerő lépések

10000

       

Lépéskör

Piros 1 szint

Fehér 2 szint

Zöld 1 szint

Nyerő lépések

10000

2833

4453

2714

13,5

A tesztelés során nagy segítséget nyújtott még gombnyomásra a manók aktuális helyzetét értékelő lépéstávolság számító és kiíró részlet:

if(e.getSource() == Btav){

for(int i=0;i<user;i++){

System.out.println(szinek[i]+" Tavja="+reltav(tabla,i));

}

}

    1. Grafika

A grafika szempontjából mérlegre kell tennünk a látványt és átláthatóságot, a másik serpenyőbe a sebességet és memóriaigényt. Minél látványosabb, animált 3D-s egy játék annál nagyobb processzor és memória igénye van. Ezért törekedni kell az egyensúlyra. Komolyabb játékprogramokat erre specializálódott tervezők készítik. Ennek az egyszerű kis programnak nem kell komoly tervezés.

A jól átláthatóság kedvéért a következőket kell megtenni:

A játékot fel lehet dobni különböző grafikai trükkökkel. Annak a manónak amelyiket megfogjuk mosolyt csaltam az arcára. Ahelyett, hogy minden manónak még egy mosolygós grafikát is rajzoljak, csak egy mosolyt terveztem, amit pluszban rárajzolok az aktív manóra. Ezzel a trükkel 3 különböző színű manót és egy jól illeszkedő mosolyt készítettem kihasználva a transparent lehetőséget.

    1. Kezelőfelületek és egyéb rutinok

A program rendelkezik egy sokfunkciós kezelőpanellal, amellyel a játszmák jól paraméterezhetőek.

A játék formon található 5 gomb, amik a játék használatához kell és kettő a tesztelést könnyíti meg.

    1. A program életciklusa

Most bemutatnám a program futását végig a megfelelő rutinikon, majd néhány kör lépéskeresési menetét.

Kezdetek kezdete a index.html oldal betöltésével indul, ahol egy utasítás szerepel a Jump.class JAVA applet letöltésére és elindítására. Miután elindult az applet, letölti a másik szükséges programfile-t a Mano.class-t.

Lefoglalja a helyet a változóknak, szálaknak, frame-eknek és inicializálja öket.

Elindul az init() inicializáló programrész, ami kialakítja a JAVA form megjelenítését, elhelyezi a feliratokat, gombokat amiket aktivizál is. A beállítás frame teljes kialakítását is elvégzi, majd megjeleníti. Lenullázza a kezdeti környezetváltozókat is. A háttérkép, manók és egyéb grafikák betöltését egy külön rutin segítségével végzi a teljes letöltés ellenőrzésével.

A JAVA egy másik program vonalat is meghív, ez a start(), amit én egy párhuzamos szál kialakítására használtam ki

////párhuzamos szál indítása

public void start(){

szal=new Thread(this);

szal.start();

}

Ezzel biztosítom, hogy a program képes legyen egy folyamatos kijelzésre, felügyeletre, amíg a gombok, és manók kezelése automatikusan végrehajtódik.

Most már elindulhat egy független szálon a run() rutin. Elvégez néhány kezdeti beállítást a változók és grafikai elemek pozicionálásán, majd meghívja a clear() és kezdes() pályainicializáló rutinokat.

Belép egy végtelen ciklusban, amely egy boolean változó igaz értékre váltásáig nem hajt végre feladatot ( ezzel lehet a végtelenciklust felfüggeszteni ). Ez a változó a beállítás formon található elemek kiválasztása és a “start” gomb aktivizálása után vált true-ra. A gomb megnyomásával a beállított értékek eltárolódnak és ennek megfelelően újra inicializálja a pályát, hátha eltér a játékosok száma az alapértelmezettől.

Visszatérve a végtelen ciklusunkhoz, ami most már folyamatosan felügyeli, hogy melyik csapat a következő lépő, ha ez esetleg gép lenne, akkor meghívja a geplep() rutint. Ez a beállított színtől függően kiszámít a megadott csapatnak egy lépést és végrehajtja, majd ellenőrzi, hogy vége van-e már a játszmának. ( Ezt néhány sorral később részletezem majd ) Ezután a beállítás formon lévő reakció csúszka által beállított ideig várakozik, ez biztosítja a gép lépéseinek könnyebb nyomon követését.

Ember ellenfél esetén nem hívja meg az előbb említett geplep() algoritmust, hanem vár. Ezalatt a drag and drop technikát és egérkezelést kihasználó rutinok használatával lehetővé teszi a lépés “kézi” végrehajtását, amit csak akkor fogad el, ha az ellenorzés() programrész engedélyezi, majd ez is lefuttatja a beer() végállás ellenőrzést.

A run() még egy kis grafikai elemet is kezel, egy kis forgó háromszög segítségével jelzi, hogy kinek kell az aktuális lépést végrehajtania, amit az eredményjelző alatt találunk meg.

Ha a játszmának vége azaz beért valaki akkor a beer() meghívja a vege() rutint, ami kiírja a nyertes színét, aktualizálja a statisztikai adatokat és letiltja a run() lépésengedélyezését, mint a program kezdetekor. Az új játszma az “Új játék” gomb megnyomásával kezdhető, ami a “start” gombhoz hasonlóan működik.

Van még pár programrész, amelyek külön indikálásra futnak le. Ilyenek a grafika megjelenítő update() és paint() rutinok, melyek a háttérkép egy részét és a rajtuk lévő feliratokat, a Mano.class részben pedig a manók grafikáját frissítik. Külön szálon indulnak gombnyomásra az előbb említett “Start” és “Új játék” akciók, valamint a “Beállítás” amely megjeleníti a beállítás formot, “Géplép” ami elinditja a geplep() rutint és az ember helyett lép, “Távolság” ami kiírja a konzolra minden manóra meghívott reltav() értékeket és végül a “Tábla” gomb akciója, ami a konzolra kirakja a tabla tömb aktuális tartalmát.

Ezek a rutinok néha megszakítva egymást hajtódnak végre, ami szinkronizálási problémákat okozna, ezért hogy kiküszöböljem, boolean jelző változókat alkalmaztam, amik biztosítják a szálak kommunikációját.

Most bemutatom, hogyan működik a mohó rekurzív lépéskereső algoritmus lépéselemzése. Kiindulásnak a kezdő állást választom, a könnyebb áttekinthetőség kedvéért. Az elemzéshez szemléltető grafikákat készítettem, melyek terjedelmük miatt a mellékletben ( 4.2 mell. ) követhetők nyomon. Az egyes állások kis ábrázolása miatt nem megoldható a manók számozása ezért az alapértelmezést a következő ábra mutatja.

Az ábrákon számozott állások láthatóak lépéstávolság értékekkel. Minden ábra első állása ( bal felső sarokban ) mindig az aktuális helyzet 1 szintű próbalépése. Ez alól az első ábra ( M1. ábra ) kivétel, itt a jelzett a kezdő felállás.

A alapértelmezetten a kezdő csapat a piros manók. A lépéskereső géplép() rutin a paramétereket átadva elindít a pirosaknak egy 2. szintű rekurziót a gepi() rutinnal.

Tehát indul az útkeresés. A lépéstávolság értéke 32. ( M1. ábra ) Először a piros “0” sorszámú manónak keresnénk utat, de mivel se lépni se ugrani nem tud a foglalt helyek miatt ezért áttér az “1”-es sorszámú manóra. Az “1”-es manó két helyre tud ugrani, ami az ábrán a “0”-ás és “1”-es állás mutat ezek 32-ről 30-ra csökkentik a távot, ezért ezt a kettőt megjegyzi. A pályát végigpásztázva nem talál más lehetőséget ezért áttér a “2”-es sorszámú manóra aminek hasonló lépéslehetősége van. ( “2”-es, “3”-as állás ) Ezek is kedvezőek ( 30 ) ezért ezek is listára kerülnek. Továbbhaladva jön a “3”-as manó, neki a pásztázás során 3 lépést talált nem túl jó eredményekkel. ( “4”-es, “5”-ös és “6”-os állás ) Ugyan így keresi a lépéslehetőségeket a “4”-es és “5” sorszámú manónak is. Minden próbának kiszámítja a lépéstávolságát. ( ezek állások bal felső sarkában találhatóak meg ). Ha nem rekurziós algoritmust alkalmaznék, akkor most a szóba jöhető lépésjavaslatok a következek lennének: “0”, “1”, “2”, “3” állások, hiszen a legnagyobb nyereséget ezek az ugrások jelentik 32 => 30.

Nézzük tovább, hiszen most jön a rekurzió lényege. Az előbb kiszámolt állásokhoz keresünk 2. lépést, azaz megnézzük melyik az a lépéskombináció, amely 2 lépésben kalkulálva jobb mint 1-1 lépésben számolva.

A következő ( M2. ábra ) az “1”-es manó “0”-ás lépését vesszük alapul ( “0/” állás ) és ehhez keresünk nagy ugrásokat. A rekurzió ismétli magát, elkezdi a “0”-ás sorszámú mamó útkeresését. Most már ez a manó is tud lépni, amit az ábra “0/0”-ás állása mutat, ami 30-ról 29-re csökkenti a lépéstávolságot. A következőkben az “1”-es sorszámú manónak keres ismét lépéseket, talál is 6 konstruktív és destruktív lépést ( “0/1”-től a “0/6”-ig ), amelyekből csak a “0/1”-es értékelhető jónak, ezért ezt is listára teszi. Majd jön a “2”-es manó próbája. Neki 4 lépése van amiből 3 darab 28-ra tudja csökkenteni a távot. Most eldobjuk az eddigi 29-es értékeket, hiszen jobbat találtunk, és az újakat feljegyzi. Most a többi következik sorban az ábrán jól látható módon. A lépéslehetőségeket elemezve a legjobbnak a “0/7”, “0/8”, “0/9” és “0/14” jelű állások tűnnek eddig. Megvan a “0”-ás kódú 2 lépéses legjobb eredmény amivel 28 távolságra juthatunk.

Nézzük az “1” jelű lépést folytatva a 2. szintű rekurzióval ( M3. ábra ). Próbálkozás a “0”-as manóval ( 30 => 29 ) ami lokálisan megjegyzendő lenne, de mivel van már egy igen jó 28-as értékünk ezért nem tároljuk. De a többi manó tesztelése során találunk újabb 28 távolságot elérő lépéskombinációval. Ezek a “1/7”, “1/8”, “1/14”, “1/19” állások, amik a többiekkel együtt eltárolandók.

Haladva sorban most a “2” jelű lépést tesztelve ( M4. ábra ) ugyancsak a legjobb eredmény a 30 => 28, ezért az itt talált 4 eredményt is tároljuk, ezek a “2/1”, “2/2”, “2/10”, “2/13”.

A “3” jelű lépésnél is hasonló a helyzet, ( M5. ábra ) a következők még eltárolandók: “3/1”, “3/2”, “3/3”, “3/14”, ezzel együtt már 16 db olyan lépéskombinációnk van ami 2 lépésben 32-ről 28-ra csökkenti a lépéstávolságot.

Egy kicsit felgyorsítva az elemzést átugrom a “4”-es jelű lépést, mivel az “5”-ös érdemleges eredmény mutat. ( M6. ábra ) Itt az első próbálkozásra a “0” sorszámú manó egyből 27-re csökkentheti a lépéseket. Ami azt jelenti, hogy az eddigi terebélyes lépésgyüjteményünket ki lehet dobni. Most már csak egy lépés szerepel a listán, ez a “5/0” jelű állás.

A következő érdekes állomás a “11” jelű lépés folytatása, ami egy újabb 27–es lépést tud produkálni a “11/0” lépésállással, ami nem meglepő hiszen ez a kezdés szimmetriájából adódóan az “5/0” tükörképe. ( M7. ábra )

A pirosak első rekurziós lépéslehetősége megszületett, két optimális lépést találva. Ez a “1”-es “5” vagy “2” manó “11” középre ugrása, ( majd a következő körben, ha nincs jobb, akkor “0”-ás cikkcakk ugrása “5/0”, “11/0” ). Ebből a két lépésből válogathat a program, a kiszámíthatóságot kerülve random kiválasztással.

A piros lépése után jöhet a fehérek válaszlépése a megbontott táblán. ( M8. ábra ) Látható, ahogy a fehér is a megszokott bejárássál keresi a lépéseket. A M9 ábrától már csak a tárolandók állását rajzoltattam meg. Látható, ahogy egyre jobb eredményeket talál. Szépen lecsökkenti 32-ről (31,30,29,27) a legutolsó ábrán látható 25-re a lépéstávolságát. Két lépésvariációt is talált, amik közül választhat. A fehér jelenlegi tervei alapján most jobban áll, mint a piros. Azt hiszem jól átlátható a program működése.

  1. Mellékletek
    1. Rendszer igények
    2. A programot JAVA nyelven írtam, ami platformfüggetlen. Internetről a böngészők segítségével is futtatható. Alkalmas internetes játék, tovább fejlesztve egymás elleni küzdelmekre is. Cél volt, hogy mérete ne legyen túl nagy és ne okozzon gondot a különböző operációs rendszereknek vagy a böngészőknek. A játék egyaránt futtatható internetről és lokális gépről is.

      A futtatáshoz szükséges fileok:

      A fő JAVA program Jump.class 11.895 byte

      A Canvas JAVA program Mano.class 4.144 byte

      A háttér képe hatter.gif 26.959 byte

      Három színű manók piros.gif 295 byte

      feher.gif 289 byte

      zold.gif 285 byte

      A manók mosolya mosoly.gif 73 byte

      Négy darab jel jel0..4.gif 275 byte


      HTML oldal index.html 225 byte

      Összesen 44.550 byte

      Ez a méret internetes letöltés esetén sem jelentős, kb. 9 mp alatt letölti egy 56 kb/s-os modem.

      A program JAVA 1.02 implementációt és annak engedélyezését igényli, ami ma már minden újabb verziójú böngészőben megtalálható. Internet Explorer 3 vagy nagyobb Netscape Navigátor 3 vagy nagyobb.

      Természetesen ha internetről szeretnénk használni, akkor internet csatlakozás is szükséges.

       

    3. Ábrák a gépi lépés szemléltetésére
    4. M1. Ábra

       

       

      M2. Ábra

      M3. Ábra

      M4. ábra

      M5. ábra

      M6. Ábra

      M7. ábra

      M8. ábra

       

       

       

       

       

       

       

       

      M9. ábra

      M10. ábra

      M11. ábra

       

      M12. ábra

       

       

       

       

       

       

    5. Irodalomjegyzék

Boruzs – Jacsmerik: Nagy játék könyv Kheirón ’97 Kiadó 2000

Mérő László Észjárások Tericium Kiadó 1997

A racionális gondolkodás korlátai és a mesterséges intelligencia

J. D. Williams Játékelmélet Műszaki Könyvkiadó 1972

Bizám – Herczeg Játék és logika Műszaki Könyvkiadó 1971

Varga Balázs Játékkoktél Minerva 1967

Lukácsy András Népek játékai Móra Könyvkiadó 1964

COGITORG kft Táblás játékok Internet 2001