Valószínűleg sokan emlékezünk még azokra az időkre, amikor a számítógépes játékok még nem a fotórealisztikus grafikájukról és a komplex online multiplayer élményről szóltak. Hanem az egyszerű, mégis addiktív játékmenetről, ami órákra a képernyő elé szegezett minket. Ebben a nosztalgikus utazásban most egy ikonikus klasszikust, a Tetris-t vesszük célba, és megmutatjuk, hogyan építheted fel a saját verziódat a megunhatatlan, de mára talán már retró státuszba került Delphi 7 segítségével. Ne ijedj meg, ha még kezdő vagy, lépésről lépésre haladunk, és a végére egy működő játékkal büszkélkedhetsz! 🚀
Miért pont Delphi 7 és miért pont Tetris?
A Delphi 7 egy legenda a maga idejében. Bár ma már vannak sokkal modernebb fejlesztői környezetek, a Delphi 7 egyszerűsége, gyorsasága és a natív Windows alkalmazások fejlesztésére való alkalmassága miatt kiváló választás egy ilyen projekthez. Különösen, ha valaki most ismerkedik a programozással, vagy csak szeretne visszanyúlni a gyökerekhez. Nincs bonyolult függőségkezelés, nincs komplex build rendszer – csak tiszta, közvetlen kódolás. A VCL (Visual Component Library) elképesztően hatékony, és pillanatok alatt összerakhatunk egy felhasználói felületet. Ugyanakkor éppen ez a közvetlenség teszi lehetővé, hogy mélyebben megértsük, hogyan működik egy játék motorháztető alatt, anélkül, hogy elvesznénk a modern keretrendszerek rétegeiben.
A Tetris pedig… nos, ki ne ismerné? Egy örökzöld klasszikus, aminek a játékmenete annyira egyszerű, hogy szinte azonnal ráérez az ember, mégis annyira komplex, hogy a profi szintre jutás rengeteg gyakorlást igényel. A logikája kiválóan alkalmas arra, hogy elsajátítsuk a játékfejlesztés alapjait: mozgást, ütközésvizsgálatot, pontszámítást és grafikus megjelenítést. Ráadásul a matematikai alapjai sem túl bonyolultak, így könnyen átláthatjuk a mechanikáját. Ez a projekt nem csupán egy játék megírásáról szól, hanem egy komplett bevezető arról, hogyan gondolkodjunk egy interaktív alkalmazás felépítésekor. 🧠
Az alapok: Project beállítás és a játéktér
Indítsuk el a Delphi 7-et, és hozzunk létre egy új „VCL Forms Application” projektet. A fő űrlapunkra (Form1) tegyünk fel néhány alapvető komponenst:
- TImage (pl.
Image1
): Ez lesz a vászon, amire a játékteret és a blokkokat rajzoljuk. Állítsuk be aWidth
ésHeight
tulajdonságait úgy, hogy egy megfelelő méretű játéktér legyen, például 300×600 pixel. - TTimer (pl.
Timer1
): Ez fogja vezérelni a blokkok esését. AzInterval
tulajdonságával állíthatjuk az esés sebességét (pl. 500 ms kezdetben). - TLabel (pl.
LabelScore
): Ez fogja megjeleníteni a játékos pontszámát. - TLabel (pl.
LabelNextBlock
): Ide rajzolhatjuk ki a következő eső blokk előnézetét.
A játék motorjának szíve a játéktér. Ezt egy kétdimenziós tömbbel reprezentáljuk, ahol minden elem egy cella állapotát írja le. Például: const GridWidth = 10; GridHeight = 20; var Grid: array[0..GridHeight-1, 0..GridWidth-1] of Integer;
A tömb értékei jelölhetik, hogy az adott cella üres (0), vagy egy bizonyos színű blokk része (1-től 7-ig, a Tetromino típusától függően). Az űrlap OnCreate
eseményében inicializáljuk a tömböt nullákkal, és hívjuk meg a játék kezdeti állapotát beállító eljárást (pl. StartGame
).
A játéktér rajzolása az Image1.Canvas
objektumon történik. Minden egyes képfrissítésnél (amikor a Timer lejár, vagy egy blokk mozog) újra kell rajzolni a teljes játékteret. Ehhez egy ciklussal végigmegyünk a Grid
tömbön, és ha egy cella nem üres, a megfelelő színnel rajzolunk oda egy négyzetet. Ehhez definiáljunk egy cellaméretet, például const CellSize = 30;
.
procedure TForm1.DrawGrid; var x, y: Integer; begin Image1.Canvas.Brush.Color := clBlack; // Háttérszín Image1.Canvas.FillRect(0, 0, Image1.Width, Image1.Height); for y := 0 to GridHeight - 1 do begin for x := 0 to GridWidth - 1 do begin if Grid[y, x] > 0 then begin // Rajzoljuk ki a blokkot a megfelelő színnel Image1.Canvas.Brush.Color := BlockColors[Grid[y, x]]; Image1.Canvas.Rectangle(x * CellSize, y * CellSize, (x + 1) * CellSize, (y + 1) * CellSize); end; end; end; end;
Ne felejtsünk el egy BlockColors
tömböt definiálni, amiben a különböző Tetromino típusok színeit tároljuk!
A Tetromino-k és mozgásuk 🧱
A Tetromino-k, vagyis a blokkok, a játék lelkei. Hét különböző formájuk van (I, O, T, S, Z, J, L). Minden egyes blokkot egy 2×2, 3×3 vagy 4×4-es logikai mátrixban tárolhatunk, ahol az egyesek jelölik az aktív részeket. Mivel minden blokknak négy lehetséges rotációja van (az ‘O’ blokk kivételével), ezeket is előre definiálhatjuk, vagy egy rotációs algoritmussal futásidőben számolhatjuk ki.
Definiáljunk egy CurrentBlock
rekordot, ami tárolja az aktuálisan eső blokk típusát, rotációját és pozícióját (X, Y koordináták). Amikor egy új blokk generálódik, véletlenszerűen válasszunk egy típust és helyezzük el a játéktér felső középső részén.
Mozgatás és ütközésvizsgálat ↔️⬇️
A blokkok mozgatása (le, balra, jobbra) és forgatása a billentyűzet események (Form1.OnKeyDown
) hatására történik. Mielőtt bármilyen mozgást végrehajtunk, elengedhetetlen egy ütközésvizsgálat (CheckCollision
) elvégzése. Ez ellenőrzi, hogy a blokk a tervezett új pozíciójában ütközne-e a játéktér falaival, aljával, vagy már rögzített blokkokkal.
Az ütközésvizsgálat a következőképpen néz ki:
- Próbáljuk meg mozgatni a blokkot a kívánt irányba (vagy forgatni).
- Ellenőrizzük a blokk minden egyes négyzetét az új pozícióján.
- Ha bármelyik négyzet a játéktéren kívülre esne, vagy egy már elfoglalt cellába kerülne, akkor ütközés van.
- Ha ütközés van, a mozgás (vagy forgatás) nem hajtódik végre, és a blokk marad a régi helyén.
Amikor a Timer1
lejár, a blokk automatikusan egy egységgel lefelé mozog. Ha az alsó mozgás ütközést eredményezne (azaz a blokk földet ér), akkor a blokkot rögzítjük a Grid
tömbben (beírjuk a megfelelő színkódot a cellákba), és generálunk egy új blokkot. Ezután jön a következő kritikus lépés.
Sorok eltüntetése és pontszámítás ✨
Miután egy blokk földet ért és rögzítettük, ellenőrizni kell, hogy keletkezett-e teljes sor. Írjunk egy CheckForLines
eljárást, ami végigmegy a Grid
tömb sorain alulról felfelé. Ha egy sorban minden cella foglalt:
- Eltüntetjük a sort (pl. a cellákat nullára állítjuk).
- Pontot adunk a játékosnak (pl. 100 pont soronként).
- Az összes felette lévő sort egyel lejjebb csúsztatjuk. Ehhez célszerű felülről lefelé haladni a sorokon, és minden sor tartalmát átmásolni az alatta lévő sorba.
- A legfelső sorokat természetesen nullákkal kell feltölteni.
Ez a mechanizmus adja a Tetris igazi varázsát és a folyamatos kihívást. Minél több sort tüntetünk el egyszerre (Tetris!), annál nagyobb bónuszpont jár. Ezt érdemes beépíteni a pontszámításba.
Játék vége és extra funkciók 🎮
A játék vége akkor következik be, ha egy újonnan generált blokk már a kezdeti pozíciójánál ütközést észlel. Ez azt jelenti, hogy a játéktér tele van. Ekkor állítsuk le a Timer1
-et, jelenítsünk meg egy „Game Over” üzenetet, és esetleg kérjük el a játékos nevét a toplista számára.
Ha már az alapok működnek, jöhetnek az extrák:
- Következő blokk előnézet: Rajzoljuk ki a
LabelNextBlock
komponensre a következő eső blokkot, hogy a játékos felkészülhessen. - Nehézségi szint: A
Timer1.Interval
értékét csökkentsük bizonyos pontszámhatároknál, hogy a blokkok egyre gyorsabban essenek. - Hanghatások: Egy-egy rövid hang a blokk mozgásakor, forgatásakor, földet érésekor, sor eltüntetésekor rengeteget dob az élményen. A
PlaySound
WinAPI függvény egyszerűen használható erre. - Pause funkció: Egy „P” billentyűvel leállíthatjuk/elindíthatjuk a Timert.
Íme egy kis ízelítő a hanghatások implementálásához, feltételezve, hogy a WinMM.pas
unitot hozzáadtad a uses
listához:
procedure TForm1.PlaySoundEffect(const SoundFileName: string); begin // A SoundFileName legyen egy WAV fájl elérési útja // SND_ASYNC: A hang lejátszása a háttérben történik, a program fut tovább // SND_FILENAME: A paraméter egy fájlnevet ad meg PlaySound(PChar(SoundFileName), 0, SND_ASYNC or SND_FILENAME); end; // Például egy blokk rögzítésekor procedure TForm1.Timer1Timer(Sender: TObject); begin // ... blokk esés, ütközésvizsgálat ... if BlockLanded then begin FixBlockToGrid; PlaySoundEffect('land.wav'); // Vagy valamilyen "fixált" hang CheckForLines; GenerateNewBlock; end; // ... end;
Véleményem a Delphi 7-ről és a retro fejlesztésről
A mai napig emlékszem arra a pillanatra, amikor először sikerült egy Delphi 7-ben írt egyszerű játékomat futtatnom. Nem volt az semmi extra, csak egy kis golyó pattogott a képernyőn, mégis mekkora dolog volt! Az az érzés, amikor a saját logikád életre kel egy vizuális környezetben, az páratlan. A Delphi 7 ebben zseniális volt. A VCL komponensei hihetetlenül gyorsan lehetővé tették a prototípusok elkészítését, és ami a legfontosabb, a direkt hozzáférés a Windows API-hoz, a GDI függvényekhez (mint az Image.Canvas-ra való rajzolás) elképesztő kontrollt adott. Nem kellett bonyolult grafikus API-kat tanulni a kezdetekhez, egyszerűen csak rajzoltál. Ez az egyszerűség, és a közvetlen visszajelzés tette lehetővé, hogy a programozás ne elvont fogalmak gyűjteménye legyen, hanem egy kreatív folyamat. Bár az „iparban” már nem ez a standard, az oktatásban, vagy egyszerű hobby projektekhez, a Delphi 7 programozás még mindig aranyat ér, különösen, ha az ember a programozás alapjait akarja elsajátítani, és megérteni, mi történik a képernyőn a modern absztrakciók mögött. Egy Tetris klón megírásával nem csupán egy játékot hozunk létre, hanem egy mélyebb megértést is szerzünk arról, hogyan működik a szoftveres grafika, az eseménykezelés és a programozási logika.
Sokan ma már el sem tudják képzelni, hogy egy ilyen egyszerű, de robusztus fejlesztői környezet mennyire felgyorsította a programozók munkáját. Az objektum-orientált Pascal nyelv, a vizuális tervezőfelület és a gyors fordítási idő kombinációja forradalmi volt. Egy Tetris játék megírása, még ha ma már nem is tűnik bonyolultnak, nagyszerű Delphi gyakorlat és egy kiváló lehetőség a logikai gondolkodás fejlesztésére.
Összegzés és további lépések 💡
Gratulálok! Ha végigcsináltad ezeket a lépéseket, akkor most már a kezedben tartod a saját Tetris játékodat, amit Delphi 7-ben írtál! Ez egy remek alapot biztosít további fejlesztésekhez. Gondolj csak bele, mennyi mindent lehetne még hozzáadni: egy high score lista, több játékmód, egyedi blokk textúrák, vagy akár hálózati multiplayer! A lehetőségek tárháza végtelen. Ne feledd, a programozás folyamatos tanulás és kísérletezés. Ne félj módosítani a kódot, próbálj ki új ötleteket, és fejleszd tovább a játékodat. Ez a projekt nem csak egy kód, hanem egy utazás a retro játékfejlesztés világába, ami megmutatja, hogy a kreativitás és a logikai gondolkodás sosem megy ki a divatból, függetlenül attól, milyen eszközök állnak rendelkezésünkre. Jó szórakozást a további kódoláshoz! 🥳