Cum se potrivesc forme de puzzle folosind Bitmasks

În acest tutorial, vă voi arăta cum să analizați o placă de plăci, să le repetați și să găsiți potriviri. Vom crea un joc în care trebuie să conectați liniile împreună pentru a forma căi complet închise fără capete deschise. Pentru a simplifica lucrurile, vom folosi bitmasking ca parte a algoritmului nostru, atribuindu-i fiecărei plăci (plus rotația) propriul număr de bit mască. Nu vă faceți griji dacă nu știți ce este bitmasking-ul. Este de fapt foarte simplu!

postări asemănatoare
  • Înțelegerea operatorilor de biți
  • Sisteme de numere: o introducere în binar, hexazecimal și mai mult
  • Faceți un joc Match-3 în Construct 2: Detectarea meciurilor

Redați demonstrația

Voi crea proiectul în C # utilizând Unity cu frameworkul Futile, dar codul va fi aplicabil pentru aproape orice cadru 2D cu câteva modificări. Iată replica Github cu întregul proiect Unity. Iar mai jos este o demonstrație pe care o vom juca:


Dați clic pe săgeți pentru a glisa rânduri și coloane. Încercați să creați forme închise.

Mergând dincolo de meciul-3

Când am început să creez Polymer, am vrut să creez altceva decât un meci de tip match-3. Pseudonimul meu intern a fost un joc "match-any". Jocurile de puzzle Match 3 sunt peste tot. În timp ce acestea pot fi cu siguranță distractiv, un motiv pentru care acestea sunt atât de comune poate fi pentru că algoritmul de a găsi un meci de trei plăci este destul de simplu.

Vroiam să pot să potrivesc mai multe plăci care să poată fi țesute în și din rânduri și coloane și să se ciocnească peste tot. Nu numai asta, dar nu am vrut un simplu joc de potrivire a culorilor. Am dorit ca meciurile să se bazeze pe anumite laturi ale plăcilor (de exemplu, o formă se poate conecta doar la alte forme pe partea stângă și dreaptă, dar nu pe partea superioară și inferioară.) Acest lucru sa dovedit a fi mult mai complex decât un algoritm normal de potrivire-3.

Acest tutorial va fi împărțit în trei secțiuni: Tile, Match Group și Board Game. În acest tutorial, voi încerca să evit cât mai mult cod specific Futile. Dacă doriți să vedeți chestia specifică, consultați codul sursă. De asemenea, nu voi arăta fiecare metodă și variabilă în acest post. Doar cele mai importante. Deci, dacă credeți că ceva lipsește, din nou, uitați-vă la codul sursă.

Ce este o mască bitmă?

Cuvântul "bitmask" se referă la modul în care puteți stoca o serie de valori adevărate / false într-o singură variabilă numerică. Deoarece numerele sunt reprezentate de cele și zerouri atunci când sunt reprezentate în binar, prin schimbarea numărului puteți să activați sau să dezactivați valorile prin comutarea dacă un bit este 1 sau 0.

Pentru mai multe detalii, vă rugăm să consultați acest articol pe operatorii bitumului și acest articol pe numere binare.


Placa

Prima noastră clasă se numește LineTile. Înainte de începerea clasei, să definim fiecare tip de țiglă.

 // Diferitele tipuri de plăci: public enum LineTileType Nub, Line, Corner, Threeway, Cross, MAX

Iată ce arată piesele:

În continuare, deoarece vom permite doar rotații de 90 de grade, să facem un lucru enum pentru rotire.

 // Folosesc acest lucru în loc de grade exacte, deoarece // ar trebui să aibă doar patru rotații distincte: public enum RotationType Rotation0, Rotation90, Rotation180, Rotation270, MAX

Următorul este a struct denumit TileIndex, care este în esență același cu a Vector2, cu excepția cazului în care se aplică în loc de plutitoare. Acesta va fi folosit pentru a urmări unde se află o placă în tabla de joc.

 public struct TileIndex public int xIndex; public int INIEX; TileIndex publică (int xIndex, int yIndex) this.xIndex = xIndex; this.yIndex = yIndex; 

În cele din urmă, să definim cele trei tipuri de conexiuni între două plăci.

 public enum TileConnectionType // O nepotrivire. Nevalid, // Placile nu se conecteaza direct, // dar nu din cauza unei margini de neegalat. ValidWithOpenSide, // Placile se conecteaza direct. ValidWithSolidMatch

În continuare, în cadrul clasei în sine, definiți o mască bitmăcată pe fiecare parte a unei plăci generice.

 // Iată biții pe care i-am atribuit fiecare parte a plăcii: // ===== 1 ===== | | // | | // 8 2 // | | // | | // ===== 4 ===== // 1 == 0001 în binar // 2 == 0010 în binar // 4 == 0100 în binar // 8 == 1000 în public binar const int kBitmaskNone = 0; public const int kBitmaskTop = 1; public const int kBitmaskRight = 2; public const int kBitmaskBottom = 4; public const int kBitmaskLeft = 8;

Apoi definiți variabilele de instanță pe care fiecare placă o va avea.

 // Reprezentarea sprite a plăcilor: public FSprite sprite; // Tipul plăcii: public LineTileType lineTileType get; setul privat; // Rotirea plăcii: public RotationType rotationType get; set privat; // Masa de biți care reprezintă tigla cu rotația ei: public mască int int [get; set privat; // Poziția plăcii pe tablă: public TileIndex tileIndex = TileIndex nou ();

Pentru constructor, creați sprite și setați-l la rotația corectă. Există un cod specific Futile aici, dar ar trebui să fie foarte ușor de înțeles.

 linia publică LinTile (LineTileType lineTileType, RotationType rotationType) this.lineTileType = lineTileType; this.rotationType = rotationType; // Configurați sprite: switch (lineTileType) caz LineTileType.Nub: sprite = nou FSprite ("lineTileNub"); pauză; caz LineTileType.Line: sprite = FSprite nou ("lineTileLine"); pauză; cazul LineTileType.Corner: sprite = nou FSprite ("lineTileCorner"); pauză; cazul LineTileType.Threeway: sprite = nou FSprite ("lineTileThreeway"); pauză; caz LineTileType.Cross: sprite = nou FSprite ("lineTileCross"); pauză; implicit: aruncați noul FutileException ("tipul plăcii de linie nevalidă");  AddChild (sprite); // Setarea rotirii sprite: switch (rotationType) caz RotationType.Rotation0: sprite.rotation = 0; pauză; caz RotationType.Rotation90: sprite.ro = 90; pauză; caz RotationType.Rotation180: Sprite.ro = 180; pauză; caz RotationType.Rotation270: sprite.ro = 270; pauză; implicit: aruncați noul FutileException ("tip rotativ invalid"); 

Acum, una dintre cele mai importante părți. Atribuiți fiecare țiglă, combinată cu rotația sa, o mască bit care este determinată de care laturi sunt solide și care sunt deschise.

 // Stabiliți o mască de biți făcând biți SAU cu fiecare parte care este inclusă în formă. // De exemplu, o piesă care are toate cele patru laturi solide (de exemplu, tigla încrucișată) ar fi // 1 | 2 | 4 | 8 = 15, care este același cu 0001 | 0010 | 0100 | 1000 = 1111 în binar. dacă (lineTileType == LineTileType.Nub) if (rotationType == RotationType.Rotation0) bitmask = kBitmaskTop; dacă (rotationType == RotationType.Rotation90) bitmask = kBitmaskRight; dacă (rotationType == RotationType.Rotation180) bitmask = kBitmaskBottom; dacă (rotationType == RotationType.Rotation270) bitmask = kBitmaskLeft;  dacă (lineTileType == LineTileType.Line) if (rotationType == RotationType.Rotation0 || rotationType == RotationType.Rotation180) bitmask = kBitmaskTop | kBitmaskBottom; dacă (rotationType == RotationType.Rotation90 || rotationType == RotationType.Rotation270) bitmask = kBitmaskRight | kBitmaskLeft;  dacă (lineTileType == LineTileType.Corner) if (rotationType == RotationType.Rotation0) bitmask = kBitmaskTop | kBitmaskRight; dacă (rotationType == RotationType.Rotation90) bitmask = kBitmaskRight | kBitmaskBottom; dacă (rotationType == RotationType.Rotation180) bitmask = kBitmaskBottom | kBitmaskLeft; dacă (rotationType == RotationType.Rotation270) bitmask = kBitmaskLeft | kBitmaskTop;  dacă (lineTileType == LineTileType.Threeway) if (rotationType == RotationType.Rotation0) bitmask = kBitmaskTop | kBitmaskRight | kBitmaskBottom; dacă (rotationType == RotationType.Rotation90) bitmask = kBitmaskRight | kBitmaskBottom | kBitmaskLeft; dacă (rotationType == RotationType.Rotation180) bitmask = kBitmaskBottom | kBitmaskLeft | kBitmaskTop; dacă (rotationType == RotationType.Rotation270) bitmask = kBitmaskLeft | kBitmaskTop | kBitmaskRight;  dacă (lineTileType == LineTileType.Cross) bitmask = kBitmaskTop | kBitmaskRight | kBitmaskBottom | kBitmaskLeft; 

Pietrele noastre sunt pregătite și suntem gata să începem să le potrivim împreună!


Grupul de meciuri

Grupurile de potrivire sunt doar: grupuri de plăci care se potrivesc (sau nu). Puteți începe pe orice țiglă dintr-un grup de potrivire și ajungeți la orice altă piesă prin legăturile sale. Toate plăcile sunt conectate. Fiecare dintre culorile diferite indică un grup de potrivire diferit. Singurul care este finalizat este cel albastru din centru - nu are conexiuni nevalabile.

Clasa grupului de potrivire este de fapt extrem de simplă. Este doar o colecție de plăci cu câteva funcții de ajutor. Aici este:

 clasa publica MatchGroup public List dale; boolul public este închis = adevărat; grupul public MatchGroup () tiles = lista nouă();  public void SetTileColor (Culoare culoare) foreach (Placă LineTile în dale) tile.sprite.color = culoare;  void public Destroy () tiles.Clear (); 

Jocul

Aceasta este de departe cea mai complicată parte a acestui proces. Trebuie să analizăm întreaga tablă, să o împărțim în grupurile individuale de meciuri, apoi să determinăm care, dacă există, sunt complet închise. O să chem această clasă BitmaskPuzzleGame, deoarece este clasa principală care cuprinde logica jocului.

Înainte de a intra în implementarea ei, totuși, să definim câteva lucruri. În primul rând este un simplu enum că săgețile vor fi atribuite pe baza cărora se află:

 // Pentru a ne ajuta să determinăm ce săgeată a fost apăsată: public enum Direcție Sus, Dreaptă, Jos, Stânga

Următorul este a struct care va fi trimis de la o săgeată care este presată, astfel încât să putem determina unde este în bord și în ce direcție se află:

 // Atunci când o săgeată este apăsată, aceasta va conține aceste date pentru a afla ce să facă cu tabla: public struct ArrowData public Direction direction; index public int; public ArrowData (direcție direcție, index int) this.direction = direcție; this.index = index; 

În continuare, în cadrul clasei, definiți variabilele de instanță de care avem nevoie:

 // conține toate plăcile hărții: public LineTile [] [] tileMap; // Conține toate grupurile de plăci conectate: Lista publică matchGroups = Listă nouă(); // Când un rând / coloană este mutat, acest lucru este setat la adevărat, astfel încât HandleUpdate să știe să se reîmprospăteze: match privat boolGroupsAreDirty = true; // Câte placi de lemn sunt la bord: int int tileMapWidth; // Cât de multe plăci sunt înalte la bord: int int tileMapHeight;

Iată o funcție care ia o țiglă și îi întoarce toate plăcile înconjurătoare (cele de deasupra, dedesubt, spre stânga și spre dreapta acesteia):

 // Metoda Helper pentru a obține toate plăcile care sunt deasupra / dedesubt / dreapta / stânga dintr-o anumită țiglă: listă privată GetTilesSurroundingTile (Placă LineTile) Listă dincolo de Tiles = lista nouă(); int xIndex = tile.tileIndex.xIndex; int yIndex = tile.tileIndex.yIndex; dacă (xIndex> 0) înconjoarăTiles.Add (tileMap [xIndex - 1] [yIndex]); dacă (xIndex < tileMapWidth - 1) surroundingTiles.Add(tileMap[xIndex + 1][yIndex]); if (yIndex > 0) înconjurătoareTiles.Add (tileMap [xIndex] [yIndex - 1]); dacă (yIndex < tileMapHeight - 1) surroundingTiles.Add(tileMap[xIndex][yIndex + 1]); return surroundingTiles; 

Acum două metode care returnează toate plăcile într-o coloană sau rând, astfel încât să le putem schimba:

 // Metoda Helper pentru a obține toate plăcile într-o anumită coloană: privat LineTile [] GetColumnTiles (int columnIndex) if (columnIndex < 0 || columnIndex >= tileMapWidth) arunca noul FutileException ("coloana nevalidă:" + columnIndex); LineTile [] coloanăTile = linia linie nouă [tileMapHeight]; pentru (int j = 0; < tileMapHeight; j++) columnTiles[j] = tileMap[columnIndex][j]; return columnTiles;  // Helper method to get all the tiles in a specific row: private LineTile[] GetRowTiles(int rowIndex)  if (rowIndex < 0 || rowIndex >= tileMapHeight) arunca noua FutileException ("coloana nevalidă:" + rowIndex); LineTile [] rowTiles = Linie nouă [tileMapWidth]; pentru (int i = 0; i < tileMapWidth; i++) rowTiles[i] = tileMap[i][rowIndex]; return rowTiles; 

Acum două funcții care vor schimba de fapt o coloană sau un rând de plăci într-o anumită direcție. Atunci când o țiglă se deplasează dintr-o margine, aceasta se învârte spre cealaltă parte. De exemplu, o deplasare spre dreapta pe un rând de Nub, Cross, Line va avea ca rezultat un rând de linii, Nub, Cross.

 // Schimbați dalele într-o coloană în sus sau în jos (cu înfășurare). void privat ShiftColumnInDirection (int coloIndex, Direcție dir) LineTile [] currentColumnArrangement = GetColumnTiles (columnIndex); int nextIndex; // Deplasați plăcile astfel încât acestea să fie în locurile corecte din matricea tileMap. dacă (dir == Direction.Up) pentru (int j = 0; j < tileMapHeight; j++)  nextIndex = (j + 1) % tileMapHeight; tileMap[columnIndex][nextIndex] = currentColumnArrangement[j]; tileMap[columnIndex][nextIndex].tileIndex = new TileIndex(columnIndex, nextIndex);   else if (dir == Direction.Down)  for (int j = 0; j < tileMapHeight; j++)  nextIndex = j - 1; if (nextIndex < 0) nextIndex += tileMapHeight; tileMap[columnIndex][nextIndex] = currentColumnArrangement[j]; tileMap[columnIndex][nextIndex].tileIndex = new TileIndex(columnIndex, nextIndex);   else throw new FutileException("can't shift column in direction: " + dir.ToString()); // Once the tileMap array is set up, actually visually move the tiles to their correct spots. for (int j = 0; j < tileMapHeight; j++)  tileMap[columnIndex][j].y = (j + 0.5f) * tileSize;  matchGroupsAreDirty = true;  // Shift the tiles in a row either right or left one (with wrapping). private void ShiftRowInDirection(int rowIndex, Direction dir)  LineTile[] currentRowArrangement = GetRowTiles(rowIndex); int nextIndex; // Move the tiles so they are in the correct spots in the tileMap array. if (dir == Direction.Right)  for (int i = 0; i < tileMapWidth; i++)  nextIndex = (i + 1) % tileMapWidth; tileMap[nextIndex][rowIndex] = currentRowArrangement[i]; tileMap[nextIndex][rowIndex].tileIndex = new TileIndex(nextIndex, rowIndex);   else if (dir == Direction.Left)  for (int i = 0; i < tileMapWidth; i++)  nextIndex = i - 1; if (nextIndex < 0) nextIndex += tileMapWidth; tileMap[nextIndex][rowIndex] = currentRowArrangement[i]; tileMap[nextIndex][rowIndex].tileIndex = new TileIndex(nextIndex, rowIndex);   else throw new FutileException("can't shift row in direction: " + dir.ToString()); // Once the tileMap array is set up, actually visually move the tiles to their correct spots. for (int i = 0; i < tileMapWidth; i++)  tileMap[i][rowIndex].x = (i + 0.5f) * tileSize;  matchGroupsAreDirty = true; 

Când faceți clic pe o săgeată (adică când butonul săgeată este eliberat), trebuie să determinăm ce rând sau coloană să se schimbe și în ce direcție.

 // Când este apăsată și eliberată o săgeată, deplasați o coloană în sus / în jos sau un rând dreapta / stânga. public void ArrowButtonReleased (butonul FButton) ArrowData arrowData = (ArrowData) buton.data; dacă (arrowData.direction == Direction.Up || arrowData.direction == Direction.Down) ShiftColumnInDirection (arrowData.index, arrowData.direction);  altfel dacă (arrowData.direction == Direction.Right || arrowData.direction == Direction.Left) ShiftRowInDirection (arrowData.index, arrowData.direction); 

Următoarele două metode sunt cele mai importante în joc. Primul ia două plăci și determină ce fel de conexiune are. Bazează conexiunea pe primul dale de intrare în metoda (numit baseTile). Aceasta este o distincție importantă. baseTile ar putea avea a ValidWithOpenSide conexiune cu otherTile, dar dacă le introduceți în ordine inversă, ar putea reveni Invalid.

 // Există trei tipuri de conexiuni pe care pot avea două plăci: // 1. ValidWithSolidMatch - aceasta înseamnă că placile sunt potrivite cu exactitate cu laturile lor solide conectate. // 2. ValidWithOpenSide - aceasta înseamnă că baza de bază are o latură deschisă care atinge cealaltă țiglă, deci nu contează ce este cealaltă țiglă. // 3. Invalid - aceasta înseamnă că partea solidă a baseTile este potrivită cu partea deschisă a celeilalte plăci, ducând la o nepotrivire. privat TileConnectionType TileConnectionType între tiles (LineTile baseTile, LineTile otherTile) int baseTileBitmaskSide = baseTile.bitmask; // Masca bitului pentru partea specifică de bază care atinge celălalt țiglă. int otherTileBitmaskSide = altTile.bitmask; // Mască bitmică pentru partea specifică de altă parte care atinge țigla de bază. // În funcție de ce parte a plăcii de bază se află o altă piesă, biți și fiecare parte împreună. cu // constanta de biți pentru acea parte individuală. Dacă rezultatul este 0, atunci partea este deschisă. Altfel, // partea este solidă. dacă (otherTile.tileIndex.yIndex < baseTile.tileIndex.yIndex)  baseTileBitmaskSide &= LineTile.kBitmaskBottom; otherTileBitmaskSide &= LineTile.kBitmaskTop;  else if (otherTile.tileIndex.yIndex > baseTile.tileIndex.yIndex) baseTileBitmaskSide & = LineTile.kBitmaskTop; otherTileBitmaskSide & = LineTile.kBitmaskBottom;  altfel dacă (otherTile.tileIndex.xIndex < baseTile.tileIndex.xIndex)  baseTileBitmaskSide &= LineTile.kBitmaskLeft; otherTileBitmaskSide &= LineTile.kBitmaskRight;  else if (otherTile.tileIndex.xIndex > baseTile.tileIndex.xIndex) baseTileBitmaskSide & = LineTile.kBitmaskRight; otherTileBitmaskSide & = LineTile.kBitmaskLeft;  dacă (baseTileBitmaskSide == 0) returnează TileConnectionType.ValidWithOpenSide; / / sideTile touching otherTile este deschisă. altfel dacă (otherTileBitmaskSide! = 0) returnează TileConnectionType.ValidWithSolidMatch; // Partea baseTile și partea laterală Tile sunt solide și potrivite. altul returnează TileConnectionType.Invalid; // partea baseTile este solidă, dar partea laterală este deschisă. Nepotrivirea! 

In cele din urma, UpdateMatches. Aceasta este cea mai importantă metodă a tuturor. Acesta este cel care trece prin bord, analizează toate piesele, determină legăturile între ele și care grupuri de potrivire sunt complet închise. Totul este explicat în comentarii.

 // Mergeți prin tablă și analizați toate plăcile, căutând meciuri: void private UpdateMatches () // Grupurile de potrivire sunt actualizate astfel încât să nu mai fie murdare: matchGroupsAreDirty = false; // Deoarece coloanele și rândurile de alunecare pot strica totul, trebuie să scăpăm de vechile grupuri de meciuri și să începem de la început. // Rețineți că există probabil o modalitate de a utiliza algoritmul în care nu trebuie să scăpăm de toate meciurile și să începem de fiecare dată (de exemplu, să actualizăm meciurile întrerupte de o schimbare), dar că pot veni mai târziu dacă // trebuie să îmbunătățiți performanța. foreach (MatchGroup matchGroup în grupurile de potrivire) matchGroup.Destroy (); matchGroups.Clear (); // Vom începe analizarea plăcii din partea stângă jos. Placa de bază actuală va fi cea pe care o pornim de la început și vom construi grupuri de potrivire. LineTile curentBaseTile = tileMap [0] [0]; Listă tileSurrounders; // Variabilă care va stoca plăci înconjurătoare de diferite plăci de bază. Listă checkedTiles = Listă nouă(); // Vom păstra aici plăcile de bază odată ce le-am analizat, astfel încât să nu le reanalizăm. MatchGroup currentMatchGroup; // Grupul de potrivire pe care îl analizăm, care include placa de bază curentă. // Faceți buclă continuu prin bord, făcând grupuri de potrivire până când nu mai există plăci pentru a face grupuri de meciuri. în timp ce (currentBaseTile! = null) // Creați un nou grup de potrivire, adăugați placa de bază curentă ca prima sa țiglă. curentMatchGroup = nou MatchGroup (); currentMatchGroup.tiles.Add (currentBaseTile); / / Faceți buclă prin plăcile care încep pe placa de bază curentă, analizați conexiunile lor, găsiți din nou o placă de bază, // și buclă și așa mai departe până când nu găsiți alte conexiuni posibile cu niciunul din plăcile din grupul de potrivire bool stillWorkingOnMatchGroup = adevărat; în timp ce (stillWorkingOnMatchGroup) // Populați lista țigleSurrounderi cu toate plăcile care înconjoară placa de bază curentă: tileSurrounders = GetTilesSurroundingTile (currentBaseTile); // Iterați prin toate plăcile înconjurătoare și verificați dacă laturile lor solide sunt aliniate cu laturile solide ale plăcii de bază: foreach (LineTileroundTile în tigleSurroundere) TileConnectionType connectionType = TileConnectionTypeBetween Tiles (currentBaseTile, surroundingTile); // Dacă există o potrivire solidă, adăugați surrogatul în grupul de potrivire. // Dacă există o nepotrivire, grupul de potrivire nu este un grup perfect de închidere. // Dacă există o nepotrivire din cauza unei laturi deschise a plăcii de bază, aceasta nu contează de fapt // deoarece nu există o parte solidă întreruptă (acest lucru se numește TileConnectionType.ValidWithOpenSide). dacă (connectionType == TileConnectionType.ValidWithSolidMatch) currentMatchGroup.tiles.Add (înconjuratTile); altfel dacă (TileConnectionTypeBetweenTiles (currentBaseTile, surroundingTile) == TileConnectionType.Invalid) currentMatchGroup.isClosed = false;  Dacă placa de bază are o latură închisă / solidă care atinge marginea plăcii, grupul de potrivire nu poate fi închis. dacă ((((actualBaseTile.bitmask & LineTile.kBitmaskTop)! = 0 && currentBaseTile.tileIndex.yIndex == tileMapHeight - 1) || ((currentBaseTile.bitmask & LineTile.kBitmaskRight)! = 0 && currentBaseTile.tileIndex.xIndex == tileMapWidth - 1) || ((currentBaseTile.bitmask & LineTile.kBitmaskBottom)! = 0 && currentBaseTile.tileIndex.yIndex == 0) || ((curentBaseTile.bitmask & LineTile.kBitmaskLeft)! = 0 && currentBaseTile.tileIndex.xIndex == 0)) currentMatchGroup.isClosed = false; // Adăugați țigla de bază la o matrice, astfel încât să nu o verificăm din nou mai târziu: dacă (! CheckedTiles.Contains (currentBaseTile)) checkedTiles.Add (currentBaseTile); // Găsiți o nouă placă de bază pe care am adăugat-o în grupul de potrivire, dar nu am analizat încă: pentru (int i = 0; i < currentMatchGroup.tiles.Count; i++)  LineTile tile = currentMatchGroup.tiles[i]; // If the checkedTiles array has the tile in it already, check to see if we're on the last // tile in the match group. If we are, then there are no more base tile possibilities so we are // done with the match group. If checkedTiles DOESN'T have a tile in the array, it means // that tile is in the match group but hasn't been analyzed yet, so we need to set it as // the next base tile. if (checkedTiles.Contains(tile))  if (i == currentMatchGroup.tiles.Count - 1)  stillWorkingOnMatchGroup = false; matchGroups.Add(currentMatchGroup);   else  currentBaseTile = tile; break;    // We're done with a match group, so now we need to find a new un-analyzed tile that's // not in any match groups to start a new one from. So we'll set currentBaseTile to // null then see if we can find a new one: currentBaseTile = null; for (int i = 0; i < tileMapWidth; i++)  for (int j = 0; j < tileMapHeight; j++)  LineTile newTile = tileMap[i][j]; if (!TileIsAlreadyInMatchGroup(newTile))  currentBaseTile = newTile; break;   if (currentBaseTile != null) break;   

Tot ce am lăsat este HandleUpdate funcţie! Fiecare cadru, actualizează grupurile de potrivire dacă au nevoie de actualizare (adică. matchGroupsAreDirty == true), și a stabilit culorile lor.

 public void HandleUpdate () if (matchGroupsAreDirty) UpdateMatches (); 

Iată ce ar arăta algoritmul dacă fiecare pas a fost animat:

Si asta e! În timp ce o parte din cod în acest lucru este specific pentru Futile, ar trebui să fie destul de clar cum să se extindă la orice altă limbă sau motor. Și pentru a reitera, sunt multe lucruri neesențiale care lipsesc în acest post. Uitați-vă la codul sursă pentru a vedea cum funcționează împreună!