Aknakereső játék készítése

Az aknakereső (Minesweeper) egy logikai játék, mely adott sor- és oszlopszámú táblázatban elrendezett mezőcellákat tartalmaz, amelyek közül, meghatározott számú, „aknát” rejt. A cél az egyes cellák felfedésével az összes akna megtalálása, illetve azok elkerülése. Ha sikerül az összes nem aknát tartalmazó cellát felfedni, akkor a játék győzelemmel befejeződik. Ha egy felfedett cellában akna van, akkor a játék vereséggel azonnal véget ér. A részletszabályok az interneten megtalálhatók, többek között például magyarul az „Aknakereső” vagy angolul a „Minesweeper” Wikipedia szócikkeknél.

Ebben a bejegyzésben e játék egy lehetséges megvalósítását mutatjuk grafikus felhasználói felülettel a tkinter modul eszköztárával megvalósítva. Ennek kinézete hasonló lesz más megvalósításokéhoz.

A játékfelület két részre tagolódik. Felül, egy sorban látható a zászlószámláló, illetve annak aktuális értéke, mellette az új játékot indító nyomógomb. Ettől jobbra pedig a játék megkezdése óta eltelt idő látható, ami másodpercenként növekszik. Ez alatt látható a játékmező, amely a felfedezésre váró cellákat négyzethálós elrendezésben jeleníti meg.

A játék indításakor a zászlószámláló az elrejtett aknák számát mutatja. Ha a játéktér bármely fel nem fedett celláján zászlót jelenítünk meg, vagyis úgy gondoljuk, hogy ott akna van, a zászlószámláló értéke eggyel csökken mutatva, hogy még mennyi felderítendő akna van hátra. Az időmérés a játékmezőn történő első cella felfedésével kezdődik, amin soha nincs akna.

A programkódot alább láthatjuk. A részletes kommentek segítik a megértést.

Elsőként a játék logikai modelljét megvalósító osztálydefiníciót mutatjuk. A MinesweeperModel osztály lényegét tekintve egy erre a játékra adaptált bináris mátrixot valósít meg, amelynek a működési elvéről a korábbi, „Bináris mátrixok megvalósítása” című bejegyzésben volt szó.

Szükségünk lesz egy időmérő órára, ami az eltelt játékidőt folyamatosan mutatja. E StopWatch nevű osztály definíciója a következő:

Az alkalmazást jelentő főprogram az alábbi, amely a fenti két osztályt használja.

A játékot a main modul szkriptként futtatásával lehet indítani, amihez Python 3.10+ verzió szükséges. A forráskód elérhető itt is: https://github.com/pythontudasepites/minesweeper

Ami az alkalmazás felépítését illeti, a játékfelület felső részét a ControlPanel osztály, a négyzethálós játékmezőt a GameField osztály valósítja meg. Az időmérőt a StopWatch osztály, a játék logikai modelljét pedig a MinesweeperModel osztály. Minezen komponensekből épül fel a MineSweeper osztály, ami egyben a főablakot is képviseli és jeleníti meg futtatáskor. Az osztálykapcsolatokat a következő ábra mutatja.

A játékmező celláit egy-egy vászon elem (Canvas példány) teszi láthatóvá. Ezek két eseményre reagálnak. Bal egérgomb kattintással felfedjük a cellát. Ekkor, ha azon nem akna van, a szomszédos cellákban rejtőző aknák száma az aktuális cellán egy címke felirataként jelenik meg. Ha viszont az aktuális cella aknát tartalmaz, akkor a játék vereséggel véget ér, és az aknaszimbólum megjelenik. De azért, hogy a rajzolást is gyakoroljuk, ezt nem egy aknát/bombát ábrázoló Unicode karakter címke feliraton való feltüntetésével tesszük, hanem a cella Canvas példányán rajzoljuk ki a vászon elemhez rendelkezésre álló rajzelemekkel. Ennek tervrajzát mutatja ez az ábra:

Ha sem az aktuálisan felfedett cella, sem a közvetlen szomszédai nem tartalmaznak aknát, akkor a cellák automatikusan mindaddig felfedődnek, amíg a szomszédaikban nem lesz legalább egy akna. Ezen cellákra ki is lesz írva a szomszédos aknák száma. Mivel ez az automatikus cellafelfedés kulcseleme a játéknak és megvalósítása némi megfontolást igényel, ezért a GameField osztály _explore_safe_fields() metódusában az algoritmus fő lépéseit kommentben kirészleteztük. Ez hasonlít a mélységi gráfbejáráshoz, amit itt rekurzióval implementáltunk.

Jobb egérgomb kattintás esetén a cella nem lesz felfedve, hanem egy zászlót ábrázoló karaktert jelenítünk meg a cella vászon elemén elhelyezett címkén, és egyúttal a zászlószámlálót eggyel csökkentjük. Újabb jobb egérgomb kattintásra a zászló eltűnik és a zászlószámláló értéke eggyel nő. Vagyis a jobb egérgomb lenyomás oda-vissza kapcsoló (toggle) üzemmódú.

Az „ÚJ JÁTÉK” feliratú gombra kattintva változatlan játékparaméterekkel (sor- és oszlopszám, valamint az elrejtett aknák száma) kezdhető új játék. Ha más paraméterekkel akarunk játékot indítani, akkor az „ÚJ JÁTÉK” gombra a jobb egérgombbal kell kattintani. A felugró párbeszédablak beviteli mezőjébe lehet megadni vesszővel elválasztott egész számokkal az új sor és oszlopszámot, valamint opcionálisan az aknák számát. Mivel az alapértelmezett játékterület 8×8 méretű 10 aknával, ez jelenik meg a párbeszédablakban kezdőértékként. Ha nem adunk meg aknaszámot, akkor a program számolja azt ki a sor- és oszlopértékekből kiadódó összcellaszám alapján. Ez a cellaszámmal úgy lesz arányos, ahogy a 10 akna a 8×8-as tábla 64 cellájával.

A következő képernyőkép alapértelmezett paraméterekkel indított játékot mutat nyert és vesztett végállapotban.

Ez az ábra pedig az alapértelmezettől eltérő paraméterekkel indított játékot mutat, ahol az új játékjellemzők beviteli ablakát is láthatjuk.

Ahogy mindig, most is ajánlott először megpróbálkozni a saját megvalósítással, és csak utána, vagy esetleg menet közben, megnézni az itt közölt megoldást, mert a problémamegoldás és a programnyelvben való jártasság így sokkal hatékonyabban fejlődik.

Egyébként pedig jó szórakozást a játékhoz!

A grafikus felhasználói felület létrehozásával, példákkal illusztrált részletes leírásával a Python tudásépítés lépésről lépésre című e-könyv „Grafikus felhasználói felület készítése” fejezete foglalkozik.

Érdekel a Python tudásépítés lépésről lépésre az alapoktól az első asztali alkalmazásig című e-könyv.