Implementarea Tetris Clearing Lines

În tutorialul meu anterior Tetris, ți-am arătat cum să se ocupe de detectarea coliziunilor în Tetris. Acum, să aruncăm o privire la celălalt aspect important al jocului: linia se șterge.

Notă: Deși codul din acest tutorial este scris folosind AS3, ar trebui să puteți utiliza aceleași tehnici și concepte în aproape orice mediu de dezvoltare a jocului.


Detectarea unei linii finalizate

Detectarea faptului că o linie a fost completă este de fapt foarte simplă; doar uita-te la matricea de matrice va face destul de evident ce sa fac:

Linia umplută este cea care nu are zero în ea - deci putem verifica un rând dat astfel:

 rând = 4; // a verifica al patrulea rând isFilled = true; pentru (var col = 0; col < landed[row].length; col++)  if (landed[row][col] == 0)  isFilled = false;   //if isFilled is still true than row 4 is filled

Cu aceasta, desigur, putem buclele prin fiecare rând și dăm seama care dintre ele sunt umplut:

 pentru (var rând = 0; col < landed.length; row++)  isFilled = true; for (var col = 0; col < landed[row].length; col++)  if (landed[row][col] == 0)  isFilled = false;   //if isFilled is still true then current row is filled 

Bine, am putea optimiza acest lucru imaginandu-ne care sunt liniile probabil pentru a fi ocupat, pe baza căruia rândurile ocupă cel mai recent bloc, dar de ce deranjează? Buclele prin fiecare element din grila 10x16 nu reprezintă o sarcină intensivă a procesorului.

Întrebarea acum este: cum facem noi clar liniile?


Metoda naivă

La prima vedere, acest lucru pare simplu: am îmbinat rândul (ele) umplut din matrice și adăugăm noi linii goale în partea superioară.

 pentru (var rând = 0; rând < landed.length; row++)  isFilled = true; for (var col = 0; col < landed[row].length; col++)  if (landed[row][col] == 0)  isFilled = false;   //remove the filled line sub-array from the array landed.splice(row, 1); //add a new empty line sub-array to the start of the array landed.unshift([0,0,0,0,0,0,0,0,0,0]); 

Dacă încercăm acest lucru pe matricea de mai sus (și apoi facem totul), obținem:

... ceea ce ne-am aștepta, nu? Mai sunt încă 16 rânduri, dar cea umplută a fost eliminată; noua linie necompletată a împins totul în jos pentru a compensa.

Iată un exemplu mai simplu, cu picsele înainte și după cele alăturate:

Un alt rezultat așteptat. Și - deși nu o voi arăta aici - același cod se ocupă și de situațiile în care mai mult de o linie este umplută dintr-o dată (chiar dacă acele linii nu sunt adiacente).

Cu toate acestea, există cazuri în care acest lucru nu face ceea ce vă puteți aștepta. Uita-te la asta:

Este ciudat să vezi că blocul albastru plutește acolo, atașat de nimic. Nu este gresit, exact - cele mai multe versiuni ale lui Tetris fac acest lucru, inclusiv ediția clasică Game Boy - pentru a putea să o lăsați la asta.

Cu toate acestea, există o serie de alte modalități populare de a face cu acest ...


Metoda Big Clump

Dacă am făcut blocul albastru solitar continuând să cadem după ce linia a fost eliminată?

Marea dificultate cu acest lucru este de fapt imaginind exact ceea ce încercăm să facem. E mai greu decât suna!

Primul meu instinct ar fi acela de a face ca fiecare bloc să cadă până când acesta a aterizat. Acest lucru ar duce la situații de genul:

... dar bănuiesc că acest lucru nu ar fi distractiv, deoarece toate lacunele se vor umple rapid. (Nu ezitați să experimentați acest lucru, totuși - ar putea fi ceva în ea!)

Vreau ca aceste blocuri portocalii să rămână conectate, dar blocul albastru să cadă. Poate ne-ar putea face blocuri să cadă dacă nu au alte blocuri la stânga sau la dreapta? Ah, dar uita-te la această situație:

Aici vreau ca blocurile albastre să cadă în toate "găurile" lor după ce linia este curățată - dar setul de mijloc de blocuri albastre are toate celelalte blocuri lângă ele: alte blocuri albastre!

("Așadar, verificați dacă blocurile se află lângă blocuri roșii", ați putea gândi, dar amintiți-vă că le-am colorat doar în albastru și roșu pentru a face mai ușor să se refere la diferite blocuri, ar putea fi orice culoare și ar fi putut fi depus în orice moment.)

Putem identifica un lucru pe care blocurile albastre din imaginea din dreapta - și blocul albastru plutitor singuratic de la început - toate în comun: ele sunt de mai sus linia care a fost eliminată. Deci, dacă în loc să încercăm să coborâm blocurile individuale, grupăm toate aceste blocuri albastre împreună și le facem să cadă ca una?

Am putea chiar să reutilizăm același cod care face ca un tetromino să cadă individual. Iată un memento, din tutorialul anterior:

 // set tetromino.potentialTopLeft a fi un rând sub tetromino.topLeft, apoi: pentru (var rând = 0; rând < tetromino.shape.length; row++)  for (var col = 0; col < tetromino.shape[row].length; col++)  if (tetromino.shape[row][col] != 0)  if (row + tetromino.potentialTopLeft.row >= landed.length) // acest bloc ar fi sub câmpul de joc altfel dacă (a aterizat [rând + tetromino.potentialTopLeft.row]! = 0 && a aterizat [col + tetromino.potentialTopLeft.col]! = 0) / spațiul este luat

Dar, mai degrabă decât de a folosi a tetromino obiect, vom crea un obiect nou al cărui formă conține doar blocurile albastre - să numim acest obiect tropăi.

Transferul blocurilor este doar o chestiune de looping prin a aterizat array, găsirea fiecărui element non-zero, completat cu la fel element în clump.shape și setarea elementului a aterizat matrice la zero.

Ca de obicei, acest lucru este mai ușor de înțeles cu o imagine:

În stânga este clump.shape array, iar în partea dreaptă este a aterizat matrice. Aici, nu mă deranjez să introduc niste rânduri goale în clump.shape pentru a păstra lucrurile mai exacte, dar puteți face acest lucru fără probleme.

Deci, ale noastre tropăi Obiectul arată astfel:

 clump.shape = [[1,0,0,0,0,0,0,0,0,0], [1,0,0,1,1,0,0,0,0,0], [ 1,0,0,1,1,0,0,0,0,1]]; clump.topLeft = rând: 10, col: 0;

... și acum purtăm în mod repetat același cod pe care îl folosim pentru a face o cadere tetromino, până când terenul se prăbușește:

 // set clump.potentialTopLeft a fi un rând sub clump.topLeft, apoi: pentru (var row = 0; row < clump.shape.length; row++)  for (var col = 0; col < clump.shape[row].length; col++)  if (clump.shape[row][col] != 0)  if (row + clump.potentialTopLeft.row >= landed.length) // acest bloc ar fi sub câmpul de joc altfel dacă (a aterizat [row + clump.potentialTopLeft.row]! = 0 && a aterizat [col + clump.potentialTopLeft.col]! = 0) / spațiul este luat

Odată ce grupul a aterizat, vom copia elementele individuale înapoi la a aterizat array - din nou, la fel ca atunci când un tetromino aterizează. Cu toate acestea, mai degrabă decât să difuzați acest lucru la fiecare jumătate de secundă și să redenumiți totul între fiecare cădere, vă sugerez să îl rulați din nou și din nou până când terenul se prăjește cât mai repede posibil și atunci făcând totul, astfel încât să pară că scade instantaneu.

Urmați acest lucru dacă doriți; iată rezultatul:

Este posibil ca o altă linie să fie formată aici, fără ca jucătorul să treacă printr-un alt bloc - deschizând strategii posibile pentru jucător care nu sunt disponibile cu metoda Naive - deci trebuie să verificați imediat liniile umplute din nou. În acest caz, nu există linii umplute, astfel încât jocul să poată continua și puteți să faceți un alt bloc.

Totul pare bun pentru metoda Clump, dar, din păcate, există o problemă, după cum se arată în acest exemplu înainte și după:


După ce linia plină dispare, ambele blocuri albastre cad în două pătrate și apoi se opresc.

Aici, blocul albastru din mijloc a aterizat - și, de vreme ce este strâns împreună cu blocul albastru din dreapta, se consideră că și acesta a "aterizat". Următorul bloc ar da naștere, iar din nou avem un bloc albastru care plutește în mijlocul aerului.

Metoda Big Clump nu este de fapt o metodă eficientă, din cauza acestei probleme neintuibile, dar ea este la jumătatea drumului spre o metodă bună ...


Metoda Sticky

Uită-te din nou la aceste două exemple:

În ambele cazuri, există o modalitate evidentă de a separa blocurile albastre de niște aglomerări separate - două aglomerări (fiecare dintr-un bloc) în primul și trei aglomerări (trei, patru și un bloc) în al doilea.

Dacă strângem blocurile de genul ăsta, și apoi facem fiecare clopot să cadă independent, atunci ar trebui să obținem rezultatul dorit! În plus, "clump" nu va mai fi un cuvânt.

Iată ce vreau să spun:

Începem cu această situație. Evident, cea de-a doua linie va fi eliminată.

Am împărțit blocurile deasupra liniei eliminate în trei aglomerări distincte. (Am folosit culori diferite pentru a identifica blocurile care se blochează împreună).

Clumpsul se încadrează în mod independent - observați cum cocoșul verde se prăbușește două rânduri, în timp ce aglomerările albastre și violete se prăbușesc după căderea doar a unuia. Linia de jos este acum umplută, astfel încât și acest lucru este curățat, precum și cele trei clumps cădea.

Cum ne dăm seama forma formațiunilor? După cum puteți vedea din imagine, este de fapt destul de simplu: grupăm toate blocurile în forme contigue - adică, pentru fiecare bloc, îl grupăm cu toți vecinii și cu vecinii vecinilor săi, și așa mai departe pe, până când fiecare bloc este într-un grup.

Mai degrabă decât să explicați exact cum să faceți această grupare, vă vom îndruma pe pagina Wikipedia pentru umplerea cu inundații, ceea ce explică mai multe moduri de a realiza acest lucru, împreună cu argumentele pro și contra fiecărui.

Odată ce ați obținut formele de aglomerație, le puteți lipi într-o matrice:

 clumps = []; clumps [0] .shape = [[3], [3]]; aglomerări [0] .topLeft = rând: 11, col: 0; clumps [1] .shape = [[0,1,0], [0,1,1], [0,1,1], [1,1,1]]; aglomerări [1] .topLeft = rând: 9, col: 3; aglomerări [2]. șir = [[1,1,1], [1,1,1], [0,1,1]]; aglomerații [2] .topLeft = rând: 10, col: 7;

Apoi, iterați fiecare grupă din cadranul matricei, amintiți-vă pentru a verifica dacă există noi linii umplute odată ce au aterizat.

Aceasta se numește metoda Sticky și este folosită în câteva jocuri, cum ar fi Tetris Blast. Imi place; este o întorsătură decentă pe Tetris, permițând noi strategii. Există o altă metodă populară care este destul de diferită ...


Provocare: Metoda Cascade

Dacă ați urmat conceptele până acum, cred că merită să încercați să implementați singură metoda Cascade ca un exercițiu.

Practic, fiecare bloc își amintește de ce tetromino face parte, chiar și atunci când un segment al tetromino-ului este distrus de o linie clară. Tetrominozele - sau părți ciudate, taiate de tetrominoase - cad sub formă de aglomerări.

Ca întotdeauna, imaginile ajută:

Un T-tetromino cade, terminând o linie. Observați cum fiecare bloc rămâne conectat la tetromino-ul original? (Vom presupune aici că până acum nu au fost șterse linii).

Linia finalizată este eliminată, ceea ce împarte Z-tetromino verde în două bucăți separate, iar bucățele de pe alte tetrominoze.

T-tetromino (sau ce a mai rămas din acesta) continuă să scadă, deoarece nu este susținută de alte blocuri.

T-tetromino terenuri, completarea o altă linie. Această linie este curățată, tăind bucăți și mai mult de tetrominoși.

După cum puteți vedea, metoda Cascade joacă destul de diferit față de celelalte două metode principale. Dacă tot nu este clar despre cum funcționează, vedeți dacă puteți găsi o copie a Quadra sau Tetris 2 (sau căutați videoclipuri pe YouTube), deoarece ambele utilizează această metodă.

Mult noroc!


Concluzie

Vă mulțumim că ați citit acest tutorial! Sper că ați învățat ceva (și nu doar despre Tetris) și că veți avea parte de provocare. Dacă faci jocuri folosind aceste tehnici, mi-ar plăcea să le văd! Vă rugăm să le postați în comentariile de mai jos sau să mă tweet la @MichaelJW.

.