Gjennomføring av Tetris Clearing Lines

I min tidligere Tetris-veiledning viste jeg deg hvordan du håndterer kollisjonsdeteksjon i Tetris. La oss nå se på det andre viktige aspektet av spillet: linjen rydder.

Merk: Selv om koden i denne opplæringen er skrevet ved hjelp av AS3, bør du kunne bruke de samme teknikkene og begrepene i nesten hvilket som helst spillutviklingsmiljø.


Registrere en ferdig linje

Det er faktisk veldig enkelt å oppdage at en linje er fylt. bare se på en rekke arrays vil gjøre det helt klart hva du skal gjøre:

Den fylte linjen er den som ikke har nuller i det - så vi kan sjekke en gitt rad slik:

 rad = 4; // sjekk fjerde rad isFilled = true; for (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

Med dette kan vi selvfølgelig løse gjennom hver rad og finne ut hvilken av dem er fylt:

 for (var row = 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 

Ok, vi kunne optimalisere dette ved å finne ut hvilke linjer som er sannsynlig å bli fylt, basert på hvilke rader den siste blokken opptar, men hvorfor bry deg? Løping gjennom hvert enkelt element i 10x16-nettet er ikke en prosessorintensiv oppgave.

Spørsmålet er nå: hvordan gjør vi det? klar linjene?


Den Naive Metoden

Ved første øyekast, dette virker enkelt: vi splitter bare den fylte raden (r) fra matrisen, og legg til nye tomme linjer øverst.

 for (var row = 0; rad < 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]); 

Hvis vi prøver dette på ovenstående matrise (og deretter gjengir alt), får vi:

... som er hva vi forventer, ikke sant? Det er fortsatt 16 rader, men den fylte har blitt fjernet; Den nye blanke linjen har presset alt ned for å kompensere.

Her er et enklere eksempel, med før og etter bilder side ved side:

Et annet forventet resultat. Og - selv om jeg ikke vil vise det her - gjelder samme kode også situasjoner der mer enn én linje fylles på en gang (selv om disse linjene ikke er tilstøtende).

Det er imidlertid tilfeller der dette ikke gjør det du kan forvente. Se på dette:

Det er rart å se at den blå blokken flyter der, festet til ingenting. Det er ikke feil, Nøyaktig - de fleste versjoner av Tetris gjør dette, inkludert den klassiske Game Boy-utgaven - slik at du kan forlate den på det.

Men det er et par andre populære måter å håndtere dette ...


The Big Clump Method

Hva om vi gjorde at den ensomme blå blokken fortsatte å falle etter at linjen ble ryddet?

Den store vanskeligheten med dette er faktisk å finne ut nøyaktig hva vi prøver å gjøre. Det er vanskeligere enn det høres ut!

Mitt første instinkt her ville være å få hver enkelt blokk blokkere ned til den hadde landet. Det ville føre til situasjoner som dette:

... men jeg mistenker at dette ikke ville være gøy, da alle hullene raskt ville bli fylt. (Ikke gjerne eksperimentere med dette, men det kan være noe i det!)

Jeg vil ha disse oransje blokkene for å holde kontakten, men den blå blokken faller. Kanskje vi kunne lage blokker hvis de ikke har noen andre blokker til venstre eller høyre for dem? Ah, men se på denne situasjonen:

Her vil jeg at de blå blokkene til alle faller inn i deres respektive "hull" etter at linjen er ryddet - men den midterste sett med blå blokker har alle andre blokker ved siden av dem: andre blå blokker!

("Sjekk så bare om blokkene er ved siden av røde blokker", kan du tenke, men husk at jeg bare har farget dem i blått og rødt for å gjøre det lettere å referere til forskjellige blokker, de kan være noen farger, og de kunne ha blitt lagt til når som helst.)

Vi kan identifisere en ting som den blå blokken i høyre bilde - og den ensomme flytende blå blokken fra før - alle er felles: de er ovenfor linjen som ble ryddet. Så, hva om i stedet for å prøve å få de enkelte blokkene til å falle, grupperer vi alle disse blå blokkene sammen og får dem til å falle som en?

Vi kan til og med bruke den samme koden som gjør en individuell tetromino fall. Her er en påminnelse, fra forrige veiledning:

 // sett tetromino.potentialTopLeft å være en rad under tetromino.topLeft, da: for (var row = 0; rad < 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) // denne blokken vil være under spillfeltet ellers hvis (landet [rad + tetromino.potentialTopLeft.row]! = 0 && landet [col + tetromino.potentialTopLeft.col]! = 0) / / plassen er tatt

Men heller enn å bruke a tetromino objekt, vi lager et nytt objekt hvis form inneholder bare de blå blokkene - la oss ringe dette objektet klynge.

Overføring av blokkene er bare et spørsmål om looping gjennom landet array, finne hvert element som ikke er null, fyll i samme element i clump.shape array, og sette elementet av landet array til null.

Som vanlig er dette lettere å forstå med et bilde:

Til venstre er clump.shape array, og til høyre er landet array. Her forstyrrer jeg ikke å fylle inn noen tomme rader clump.shape for å holde ting renere, men du kan gjøre det uten problemer.

Så, vår klynge objekt ser slik ut:

 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 = rad: 10, kol: 0;

... og nå har vi bare gjentatte ganger kjørt samme kode som vi bruker for å få et tetromino fall, til klumpen lander:

 // sett clump.potentialTopLeft å være en rad under clump.topLeft, da: for (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) // denne blokken vil være under spillfeltet ellers hvis (landet [rad + clump.potentialTopLeft.row]! = 0 && landet [col + clump.potentialTopLeft.col]! = 0) / / plassen er tatt

Når klumpen har landet, kopierer vi de enkelte elementene tilbake til landet array - igjen, akkurat som når en tetromino lander. Men i stedet for å kjøre dette hvert halve sekund og gjenopprette alt mellom hvert fall, foreslår jeg at du kjører det igjen og igjen til klumpen lander, så raskt som mulig, og deretter gjør alt, slik at det ser ut som det faller umiddelbart.

Følg dette gjennom hvis du vil; her er resultatet:

Det er mulig at en annen linje blir dannet her, uten at spilleren må slippe en annen blokk - åpne mulige spillestrategier som ikke er tilgjengelige med Naive-metoden - så du må umiddelbart sjekke om fylte linjer igjen. I dette tilfellet er det ingen fylte linjer, slik at spillet kan fortsette, og du kan gyte en annen blokk.

Alt virker bra for Clump-metoden, men dessverre er det et problem, som vist i dette før-og-etter-eksempelet:


Etter at den fylte linjen forsvinner, faller begge blå blokkene to firkanter og stopper deretter.

Her har den blå blokken i midten landet - og siden den er klumpet sammen med den blå blokken til høyre, anses den som "landet" også. Den neste blokken ville gyte, og igjen har vi en blå blokk som flyter midt i luften.

Big Clump-metoden er egentlig ikke en effektiv metode, på grunn av dette utilsiktede problemet, men det er halvveis til en god metode ...


Den klare metoden

Se igjen på disse to eksemplene:

I begge tilfeller er det en åpenbar måte å skille de blå blokkene i separate klumper - to klumper (hver av en blokk) i den første og tre klumper (i tre, fire og en blokk) i den andre.

Hvis vi klumper blokkene sånn, og så gjør hver klump faller uavhengig, så bør vi få det ønskede resultatet! I tillegg vil "clump" ikke lenger synes å være et ord.

Her er hva jeg mener:

Vi starter med denne situasjonen. Åpenbart kommer den andre linjen til å bli ryddet.

Vi deler blokkene over den rydde linjen i tre forskjellige klumper. (Jeg har brukt forskjellige farger for å identifisere hvilke blokker som er klumpet sammen.)

Klumpene faller uavhengig - legg merke til hvordan den grønne klumpen faller to rader, mens de blå og lilla klumper lander etter å ha falt bare en. Bunnlinjen er nå fylt, så dette blir slettet også, og de tre klumper faller.

Hvordan finner vi ut klumpens form? Vel, som du ser fra bildet, er det faktisk ganske enkelt: Vi grupperer alle blokkene opp i sammenhengende former - det vil si for hver blokk, vi grupperer den sammen med alle naboene, og naboens naboer, og så på, til hver blokk er i en gruppe.

I stedet for å forklare nøyaktig hvordan du gjør denne gruppen, vil jeg peke deg på Wikipedia-siden for flomfylling, noe som forklarer flere måter å oppnå dette, sammen med fordeler og ulemper ved hver.

Når du har klumpene dine, kan du holde dem i en rekkevidde:

 klumper = []; klumper [0] .shape = [[3], [3]]; klumper [0]. topLeft = rad: 11, kol: 0; klumper [1] .shape = [[0,1,0], [0,1,1], [0,1,1], [1,1,1]]; klumper [1] .topLeft = rad: 9, kol: 3; klumper [2]. shape = [[1,1,1], [1,1,1], [0,1,1]]; klumper [2]. topLeft = rad: 10, kol: 7;

Deretter bare gjenta hver klump i gruppen som faller, og husk å sjekke etter nye fylte linjer når de har landet.

Dette kalles Sticky-metoden, og den brukes i noen få spill, for eksempel Tetris Blast. Jeg liker det; Det er en anstendig vri på Tetris, som gir nye strategier. Det er en annen populær metode som er ganske annerledes ...


Utfordring: Cascade Metoden

Hvis du har fulgt konseptene så langt, synes jeg det er verdt å prøve å implementere Cascade-metoden selv som en øvelse.

I utgangspunktet husker hver blokk hvilken tetromino den var en del av, selv når et segment av den tetromino blir ødelagt av en linjeklar. Tetrominoene - eller rare, hakkede deler av tetrominoer - faller som klumper.

Som alltid hjelper bildene:

En T-tetromino faller, fullfører en linje. Legg merke til hvordan hver blokk forblir koblet til sin opprinnelige tetromino? (Vi antar her at ingen linjer har blitt fjernet så langt.)

Den fullførte linjen er ryddet, som deler den grønne Z-tetromino i to separate stykker, og hugger stykker av andre tetrominoer.

T-tetromino (eller hva er igjen av det) fortsetter å falle, fordi det ikke holdes opp av andre blokker.

T-tetromino lander, fullfører en annen linje. Denne linjen er ryddet, hakk av stykker av enda flere tetrominoer.

Som du kan se, spiller Cascade-metoden litt annerledes enn de to andre hovedmetodene. Hvis du fortsatt er uklart om hvordan det fungerer, kan du se om du kan finne en kopi av Quadra eller Tetris 2 (eller se opp videoer på YouTube), da de begge bruker denne metoden..

Lykke til!


Konklusjon

Takk for at du leser denne opplæringen! Jeg håper du har lært noe (og ikke bare om Tetris), og at du skal ta en utfordring. Hvis du lager noen spill ved hjelp av disse teknikkene, vil jeg gjerne se dem! Vennligst legg inn dem i kommentarene nedenfor, eller tweet meg på @MichaelJW.

.