Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

Graph Theory? What algorithm do I use to find matching tiles in a 2D array that...

Discussion in 'General Discussion' started by KingTooTall, Dec 15, 2014.

  1. KingTooTall

    KingTooTall

    Joined:
    Oct 17, 2014
    Posts:
    61
    Hello all again, (THIS post is related:)
    I am here with my next question to my remake of the SEGA game columns.
    What algorithm do I use to find matching tiles of 3 OR more tiles that are of the same color in a 2D array, AND that are on the SAME line in any of the 8 directions. (horz,vert,diag.)
    I was also hoping to discuss some presuedo code. Each grid is 6 tile wide, and 16 tiles tall. I have three 2d arrays. one for my Visual grid of game objects, one for the main 2D grid to handle the core logic of the game made of up my special class Type "Gem" where each tile basically keeps track of its color, x and y coords, and a few other things at the moment and finally just a temp storage area where i COPY the main grid to for manipulation of match making of tiles of same color, resettling the board, etc. I have been reading about DFS and BFS algorithms, I am not sure which one that I have already written, but currently I have it so that when my CheckForNeighbor routine finds a matching gem that is adjecent to my "root" gem its doesnt return, but rather keeps on looking in its current direction one tile further from the "root" gem and so on until we hit the edge of the board, and empty tile, or a NON-matching gem... Which one I should use because it is best suited for my situation? with the above method that I have already written it will not "find" all of the correct matches, it does almost all of the time, but in situations where is starts to check "UP" and finds 3 matching gems in a row one after the other, the check WONT go left or right, as its trying to find "the furthest" gem away that is connected to the 1st gem, but never goes back to revisit the child nodes it found along the way to "check its neighbors of neighbors". and I wrote it that way trying to avoid the "flood fill" effect when trying to make matches that follow the 3 in a row ON the same line rule (which the flood fill does not follow, it just finds all the connecting neighbors of neighbors until it exhausts all options, which i need all neighbors of neighbors, BUT IN A STRAIGHT LINE of 3 or MORE....).

    So In a nutshell how would I use this BFS algorithm in my said situation with the Rules of matching as the COLUMNS game has them laid out?:) or should i be using something else?

    Again, I use C#, and I'm not a unity pro, but I would like to attempt to understand how to implement the best algorithm into this game I'm coding.

    Thanks
    King


    "In graph theory, breadth-first search (BFS) is a strategy for searching in a graph when search is limited to essentially two operations: (a) visit and inspect a node of a graph; (b) gain access to visit the nodes that neighbor the currently visited node. The BFS begins at a root node and inspects all the neighboring nodes. Then for each of those neighbor nodes in turn, it inspects their neighbor nodes which were unvisited, and so on. "
     
  2. angrypenguin

    angrypenguin

    Joined:
    Dec 29, 2011
    Posts:
    15,617
    I solved a similar problem recently. I have a 2D array where all of my tokens are at indices which correspond to their position. I walk this array in each possible direction and count how many of a colour I found in a row. This is a matter of checking the colour of the current token against the colour of the last one I looked at. If it's the same I increment a counter, if it's different (or if there was no previous token) reset the counter to 1 and optionally take additional action depending on what the value of the counter had been.
     
  3. KingTooTall

    KingTooTall

    Joined:
    Oct 17, 2014
    Posts:
    61
    penguin, thank you for a fast response. currently i am using a LIST to hold all base gems when the dropping box hits the bottom of the board or another gem below. that starts my check match routine which does pretty much what you have described... hence my question still, i am searching the "end" of the line gem out, but not going back to each gem found along the way ,making it parent and checking neighbors again, im only doing the "360 degree" check basically one line of tiles at a time, not one gem at a time, then im not checking each child of that gem for neighbirs all around, im just checking the child node to the direction we are checking already. i was worried that if i checked the neighbors of the child nodes, i would end up with ALL connected tiles, nit just 3 in a row,in a straight line...thanks King
     
  4. angrypenguin

    angrypenguin

    Joined:
    Dec 29, 2011
    Posts:
    15,617
    I don't understand why parenting has anything to do with anything here. You have a grid of things, and all you need to do is identify certain patterns in that grid of things, right?

    I'd start by making sure my data is laid out in a manner that suits the problem I'm solving. In this case, I don't see a plain ol' list as offering much help. How are you even identifying adjacent pieces?
     
  5. KingTooTall

    KingTooTall

    Joined:
    Oct 17, 2014
    Posts:
    61
    i have each tile store its current x and y positions right in each tile, of my custom class type,including the color of the tile also. so when a player drops the gem dropping box to the bottom of the board, i store those 3 gems as ROOT tiles to start checking neighbors from thier x,y positions. after the matching list is destroyed,and the board resettled, i use the same list to cycle through recursively each of those tiles that fell down due to gaps in destroyed matches,as the new root tile list... repeat... its the way my check neighbor method is working (the one that checks for contiguous matches in a row ) incorrectly. so im not sure if understood the bfs algorithm would do what im asking, and how id use it to iterate the board amd track correct matches? sorry im noobish here.
     
  6. angrypenguin

    angrypenguin

    Joined:
    Dec 29, 2011
    Posts:
    15,617
    How are you finding adjacent gems? I understand that a gem stores its position, but how do you get from that to knowing what is adjacent?
     
  7. R-Lindsay

    R-Lindsay

    Joined:
    Aug 9, 2014
    Posts:
    287
    King,

    breadth/depth first searching is for tree structures. I don't see how they can help you here.

    Do you still have a board object with an array [x,y] of gems? If so, then you can find adjacent gems trivially.
    e.g. [x-1, y] where x > min_x and [x+1, y] where x < max_x.

    Your code should at least be able to do the following:
    given a gem [x,y] find:
    1. horizontal matches that include this gem
    2. vertical matches that include this gem
    3. diagonal matches that include this gem

    If your current code cannot do this, write a method (or 3) so that it can.
     
  8. angrypenguin

    angrypenguin

    Joined:
    Dec 29, 2011
    Posts:
    15,617
    I think that BFS and DFS apply because they're somehow searching for (and building a tree of) adjacent nodes as they go.
     
  9. KingTooTall

    KingTooTall

    Joined:
    Oct 17, 2014
    Posts:
    61
    r.lindsay, thank you. once again you understood what i was asking. lol i did write code that finds matches of 3 or more horz and vert only at the moment. but the method just checks for a run from the list of base gems, not checking all directions from each adjecent matching tile that was found. so if there is 2 blue tiles on the same x axis the next gem set i drop down, lets say that it makes up 3 blue gems vertically, my check method will find the tile above ,above,and itself, so thats three...BUT the top gem connects the 2 blue gems i described earlier on the same x axis to for a horz match of 3, but it doesnt register as my check routine is not setup correctly... i think im doing a terrible job of trying to explain this. sorry about that.
     
  10. KingTooTall

    KingTooTall

    Joined:
    Oct 17, 2014
    Posts:
    61
    penguin thats exactly how i was trying to apply bfs to my check neighbor method... so i agree with you unless im over complicating the game matching logic?
     
  11. angrypenguin

    angrypenguin

    Joined:
    Dec 29, 2011
    Posts:
    15,617
    R.Lindsay and I are saying the same thing. You're very much over complicating it. If you get your data layout right then this is a non-issue and you shouldn't need anything as heavy as a DFS or BFS.

    In my grid game, searches are easy because I don't have to do anything to "find" adjacent cells. I just know where they are intrinsically. If I'm looking at cell [x,y] I know that adjacent cells are all of [x+/-1,y+/-1]. I just request that cell from the grid and use it. Job done.
     
    R-Lindsay likes this.
  12. Stoven

    Stoven

    Joined:
    Jul 28, 2014
    Posts:
    171
    Even if you weren't using a Grid, if you were using a Graph of nodes, you could do faster searches at the cost of more memory. As an example, storing references to the nodes in a dictionary where each node is mapped to a unique ID, and each node has a list of references for its neighboring nodes. Then you can do a fast search for any particular node and its neighbors, but again the cost is memory for speed (which is usually the case in these kinds of situations).
     
  13. smd863

    smd863

    Joined:
    Jan 26, 2014
    Posts:
    292
    Finding adjacent gems is trivial, but to find all matches of a certain length you need to search the adjacency graph for each gem. You just want to use dynamic programming (break the problem into sub-problems and remember the answer) with a simple recursive algorithm.

    Pseudocode:
    Code (csharp):
    1.  
    2. For each gem
    3.      For each direction
    4.           call gem.matches(direction)
    5.      For each line
    6.           matches = 1 + sum of two opposite directions for that line
    7.      If (matches > 3)
    8.           store gem in a list to process later
    9.  
    10. function gem.matches(direction)
    11. {
    12.   if gem.isEvaluated[direction] = false
    13.        if gem in that direction matches
    14.             gem.matches[direction] = 1 + adjacent_gem(direction).matches(direction)
    15.             gem.isEvaluated[direction] = true
    16.        else
    17.             gem.matches[direction] = 0
    18.             gem.isEvaluated[direction] = true  
    19.  
    20.   return gems.matches[direction]
    21. }
    22.  
    23. function adjacent_gem(direction)
    24. {
    25.      simply return the adjacent gem in that direction
    26. }
    27.  
    When getting the matches from adjacent gems you use lazy evaluation. If the gem already knows the number of matches in that direction, then you simply return it. If not, then you evaluate it. If you've already visited a gem, all of its matches for every direction should already be evaluated.

    It boils down to DFS, but you want to remember the matches in each direction so you don't have to revisit gems multiple times. If you've already visited row 3 and you are currently visiting row 4, all the gems from row 3 should already know how many matches they have below them (straight or diagonally).

    Once you've checked all the directions for a gem, you get the longest match for each line by adding the matches in two opposite directions and plus one (for the current gem). Then you can simply store a reference gems with a longest match of 3 or greater in a list to process later.

    Of course, once you've finished processing your results remember to clear all the match/isEvaluated information for the next time you run your algorithm.
     
  14. angrypenguin

    angrypenguin

    Joined:
    Dec 29, 2011
    Posts:
    15,617
    That approach will certainly work, but it's a huge amount of effort compared looping over a grid with a counter.
     
    zombiegorilla likes this.
  15. KingTooTall

    KingTooTall

    Joined:
    Oct 17, 2014
    Posts:
    61
    Hello all,
    I wanted to post my code that I have written for checking the running color matches of 3 or more in a straight line, and I also want to include my Resettle Board method also. My Horz, Vert, and Diag match checking seems to be working correctly. I have called each directional check method by itself (and I still am just for testing) and tested it visually, and made sure it was doing what it was supposed to on the grid behind the scenes. I left the gaps on the grid visually and empty on the grid board so I could make sure that my match routine was matching directions, color, and basically doing whats its supposed to be, and doing it correctly, LoL. Have you ever added like 100 print statements after each line of your code to "tell" you in the console whats actually going on vs. what you THOUGHT was happenning? Nevermind...(*Grin*) anyways, If you folks could just skim over my methods here and make sure I have written what we have discussed so far correctly, I will get to my point:
    The matches seem to be correct when I call each method one after the other as my CheckForMatch method shows, or If I were (and will) add them all together so its one big "8 direction Check method" it still works fine. I have checked this again, Visually, and In memory (printed to console what was at location "x,y" from the 2D grid after visual blocks are destroyed at same location)
    The problem seems to be SOMETIMES, AFTER I haved called the directional check, it finds matches, destroys them,THEN resettles the board... after resettling, the check for match is called again, THEN it seems to find matches that are not color related, or in line with whatever portion of the check routine was running (i.e it was searching up and to the left, and found matches, destroys the correct matches, but also will seem to destory some random block that was not "in line" with the direction it was giong!)
    Can anyone see any obvious mistakes that I dont here? I am happy to post other code if needed or explain further if needed.
    As always, you guys have been a great help, THANKS
    King

    Edit: Uploaded two pictures..
    Here is the BEFORE and AFTER of the grid. The three black dots just indicate the source of the check, the yellow gem on the bottom is the START gem of recursion.
    NOTICE it did NOT register any of the matches but the ones going LEFT. wtf?!


    CheckForMatch:

    Code (CSharp):
    1.  
    2. // **CHECKING FOR MATCHING GEMS OF 3 OR MORE**
    3. public static void CheckForMatch(GameBoard board)
    4.         {
    5.             //Start with the 1st tile in the list of Base Tiles
    6.             for(int i = 0;i < topOfRecursionBaseTiles.Count;i++)
    7.             {
    8.                 //End list that will contain all matches of 3 or more found in match 3 pass
    9.                 finalListOfGemsToDestroy.Clear();
    10.                 //Make a Copy of 2D gridboard of Type "Gem" for Manipulation purposes
    11.                 GameBoard.CopyGridBoard(board, CoreGameManager.gridBoardCopy);
    12.                 //Iterate the List of Parent/Master Base Tiles
    13.                 parentBaseTile = topOfRecursionBaseTiles[i];
    14.                 //Call each matchmaking method seperatly for now, we will combine all dir checking together after testing...
    15.                 horzMatchCount = 0;
    16.                 collector.Clear ();
    17.                 CheckForNeighborsHorz(parentBaseTile,parentBaseTile.Row,parentBaseTile.Column);
    18.                 vertMatchCount = 0;
    19.                 collector.Clear ();
    20.                 CheckForNeighborsVert(parentBaseTile,parentBaseTile.Row,parentBaseTile.Column);
    21.                 diagzMatchCount = 0;
    22.                 collector.Clear ();
    23.                 CheckForNeighborsDiagz(parentBaseTile,parentBaseTile.Row,parentBaseTile.Column);
    24.                 //If there were any set of 3 matches or more found, then go destroy gems, resettle board, check for matches again...
    25.                 if(finalListOfGemsToDestroy.Count >= minNumGemsForChain)
    26.                 {
    27.                     //Destroy all VISUAL game objects in the FINAL LIST of Gems to remove off the VisualGrid
    28.                     DestroyMatchingGems(finalListOfGemsToDestroy);
    29.                     //We removed gems from the grid, now FILL in the gaps left behind, and add NEW PARENT tiles that were found above the empty gaps,as the gapfill may possibly have caused more matches
    30.                     SettleBlocks(CoreGameManager.gridBoard);
    31.                     //The resettle of the board may have caused NEW matches, go check our ParentTile list again until no more base tiles are found
    32.                     CheckForMatch(CoreGameManager.gridBoard);
    33.                 }
    34.            
    35.             }
    36.    
    Directional Checks:

    Code (CSharp):
    1.        
    2. //Check for same color tiles in a STRAIGHT line of 3 or more HORIZONTALLY
    3. public static void CheckForNeighborsHorz(Gem currentBaseTile, int x, int y)    
    4.     {
    5. /******************************************************
    6. * If this function finds that the cell is NULL, then we skip tile. (Null = Already Tested this Tile)
    7. * If this function finds that the cell is EMPTY, then we skip tile.(Empty = its..EMPTY... dont need to test)
    8. * parentBaseTile is STATIC and holds the ORIGINAL base tile this check started from and is only changed to a new root gem by CheckForMatch()
    9. ******************************************************/
    10. //Horz Checks:
    11.             //Check LEFT, Make sure we are inside the board boundry and the tile we are looking at is NOT NULL, and NOT EMPTY
    12.             if(x - 1 >= 0 && CoreGameManager.gridBoardCopy[x-1,y] != null && CoreGameManager.gridBoardCopy[x-1,y].GemColorType != Gem.GEMTYPE.EMPTY)
    13.             {
    14.                 isCheckingNeighbors = true;
    15.                 while (isCheckingNeighbors)
    16.                 {
    17.                     //Since we will be LOOPING our check to the left we want to make sure we AGAIN are in bounds of the grid...etc.etc.
    18.                     if(x - 1 >= 0 && CoreGameManager.gridBoardCopy[x-1,y] != null && CoreGameManager.gridBoardCopy[x-1,y].GemColorType != Gem.GEMTYPE.EMPTY )
    19.                     {
    20.                         //Test the tile to see if colors match
    21.                         TestTile(x-1,y);
    22.                         //If colors DO MATCH
    23.                         if(isGemMatching == true)
    24.                         {
    25.                         //Add it to Temp list to track all the same color gems that are in the SAME DIRECTION (e.g All gems found to the LEFT of the Root gem)
    26.                             collector.Add(CoreGameManager.gridBoardCopy[x-1,y]);
    27.                             //Make the tile (to the left in this case) that was just checked AND MATCHED, our NEW PARENT tile to check from
    28.                             currentBaseTile = CoreGameManager.gridBoardCopy[x-1,y];
    29.                             //Make tile NULL as we have checked this tile...and our parent tile null as we are using it to check from and we dont want to check any tile twice?
    30.                             CoreGameManager.gridBoardCopy[x-1,y] = null;
    31.                             CoreGameManager.gridBoardCopy[x,y] = null;
    32.                             //Increment the Horz Match Counter (for later use?)
    33.                             horzMatchCount ++;
    34.                             x = x-1;
    35.                             //y stays the same, we are HORZ checking
    36.                         }
    37.                         else
    38.                         //The gems do NOT MATCH
    39.                         {
    40.                         isCheckingNeighbors = false;//Set the LOOP Exit condition.
    41.                         }
    42.                     }
    43.                     else
    44.                     //tile is EMPTY, or we are OFF the edge of the grid so  no need to check further, kill loop.
    45.                     {
    46.                         isCheckingNeighbors = false;
    47.                     }
    48.                 }
    49.             //Put ROOT tile back, Start check to RIGHT FROM the ROOT tile that we started checking left from....
    50.             currentBaseTile = parentBaseTile;
    51.             x = currentBaseTile.Row;
    52.             y = currentBaseTile.Column;
    53.             }
    54.        
    55. //CHECKING RIGHT....
    56.             if(x + 1 <= GameBoard.BoardWidth-1 && CoreGameManager.gridBoardCopy[x+1,y] != null && CoreGameManager.gridBoardCopy[x+1,y].GemColorType != Gem.GEMTYPE.EMPTY)
    57.             {
    58.                 isCheckingNeighbors = true;
    59.                     while (isCheckingNeighbors)
    60.                     {
    61.                         if(x + 1 <= GameBoard.BoardWidth-1 && CoreGameManager.gridBoardCopy[x+1,y] != null && CoreGameManager.gridBoardCopy[x+1,y].GemColorType != Gem.GEMTYPE.EMPTY )
    62.                         {
    63.                             TestTile(x+1,y);
    64.                             if(isGemMatching == true)
    65.                             {
    66.                                 collector.Add(CoreGameManager.gridBoardCopy[x+1,y]);
    67.                                 currentBaseTile = CoreGameManager.gridBoardCopy[x+1,y];
    68.                                 //Make block NULL as we have checked this block...and our parent block null as we are using it to check from and we dont want to check it twice.
    69.                                 CoreGameManager.gridBoardCopy[x+1,y] = null;
    70.                                 CoreGameManager.gridBoardCopy[x,y] = null;
    71.                                  x = x+1;
    72.                                 //y stays the same, we are HORZ checking
    73.                                 horzMatchCount++;
    74.                             }
    75.                             else
    76.                             {
    77.                                 isCheckingNeighbors = false;//Set the LOOP Exit condition.
    78.                             }
    79.                         }
    80.                         else
    81.                         {
    82.                             isCheckingNeighbors = false;
    83.                         }
    84.                     }
    85.                     currentBaseTile = parentBaseTile;
    86.                     x = currentBaseTile.Row;
    87.                     y = currentBaseTile.Column;
    88.             }
    89.             horzMatchCount = 0;
    90.        
    91.             //If we Found 3 or more of same color tile in a straight line (horz in this case) then, Process collector
    92.             if(collector.Count >= minNumGemsForChain - 1)
    93.             {
    94.                 //If the final list already has the ROOT gem in it, then DONT add the base gem to the list....
    95.                 if(!finalListOfGemsToDestroy.Contains(parentBaseTile))
    96.                 {
    97.                     finalListOfGemsToDestroy.Add (parentBaseTile);
    98.                 }
    99.                 //There were 3 or more gems of same color found in a row on the same line, add ALL gems to final list of Gems to Destroy
    100.                     finalListOfGemsToDestroy.AddRange(collector);
    101.  
    102.             }
    103.     }
    Code (CSharp):
    1.  
    2. //Check for same color tiles in a STRAIGHT line of 3 or more VERTICALLY    
    3. public static void CheckForNeighborsVert(Gem currentBaseTile, int x, int y)    
    4.     {
    5. //Vert Checks:
    6. //UP
    7.             if(y + 1 <= GameBoard.BoardHeight-1 && CoreGameManager.gridBoardCopy[x,y+1] != null && CoreGameManager.gridBoardCopy[x,y+1].GemColorType != Gem.GEMTYPE.EMPTY)
    8.             {
    9.                 isCheckingNeighbors = true;
    10.                 while (isCheckingNeighbors)
    11.                 {
    12.                     if(y+1 <= GameBoard.BoardHeight-1 && CoreGameManager.gridBoardCopy[x,y+1] != null && CoreGameManager.gridBoardCopy[x,y+1].GemColorType != Gem.GEMTYPE.EMPTY )
    13.                     {
    14.                         TestTile(x,y+1);
    15.                         if(isGemMatching == true)
    16.                         {
    17.                             collector.Add(CoreGameManager.gridBoardCopy[x,y+1]);
    18.                             currentBaseTile = CoreGameManager.gridBoardCopy[x,y+1];
    19.                             CoreGameManager.gridBoardCopy[x,y+1] = null;
    20.                             CoreGameManager.gridBoardCopy[x,y] = null;
    21.                             vertMatchCount++;
    22.                             //x stays the same, we are VERT checking    
    23.                             y = y+1;
    24.                         }
    25.                         else
    26.                         {
    27.                             isCheckingNeighbors = false;//Set the LOOP Exit condition.
    28.                         }
    29.                     }
    30.                     else
    31.                     {
    32.                         isCheckingNeighbors = false;
    33.                     }
    34.                 }
    35.                 currentBaseTile = parentBaseTile;
    36.                 x = currentBaseTile.Row;
    37.                 y = currentBaseTile.Column;
    38.          }
    39.        
    40. //DOWN        
    41.             if(y - 1 >= 0)
    42.             {
    43.                 isCheckingNeighbors = true;
    44.                 while (isCheckingNeighbors)
    45.                 {
    46.                     if(y - 1 >= 0 && CoreGameManager.gridBoardCopy[x,y-1] != null && CoreGameManager.gridBoardCopy[x,y-1].GemColorType != Gem.GEMTYPE.EMPTY )
    47.                     {
    48.                         TestTile(x,y-1);
    49.                         if(isGemMatching == true)
    50.                         {
    51.                             collector.Add(CoreGameManager.gridBoardCopy[x,y-1]);
    52.                             currentBaseTile = CoreGameManager.gridBoardCopy[x,y-1];
    53.                             CoreGameManager.gridBoardCopy[x,y-1] = null;
    54.                             CoreGameManager.gridBoardCopy[x,y] = null;
    55.                             //x stays the same, we are VERT checking    
    56.                             y = y-1;
    57.                             vertMatchCount++;
    58.                         }
    59.                         else
    60.                         {
    61.                             isCheckingNeighbors = false;//Set the LOOP Exit condition.
    62.                         }
    63.                     }
    64.                     else
    65.                     {
    66.                     isCheckingNeighbors = false;
    67.                     }
    68.                 }
    69.                  currentBaseTile = parentBaseTile;
    70.                  x = currentBaseTile.Row;
    71.                 y = currentBaseTile.Column;
    72.             }
    73.                 vertMatchCount = 0;
    74.  
    75.                 if(collector.Count >= minNumGemsForChain - 1)
    76.                 {
    77.                     if(!finalListOfGemsToDestroy.Contains(parentBaseTile))
    78.                     {
    79.                         finalListOfGemsToDestroy.Add (parentBaseTile);
    80.                     }
    81.                         finalListOfGemsToDestroy.AddRange(collector);
    82.                 }
    83.     }
    84.    
    Code (CSharp):
    1.    
    2. //Check for same color tiles in a STRAIGHT line of 3 or more DIAGONALLY(any of the 4 dir's)
    3. public static void CheckForNeighborsDiagz(Gem currentBaseTile, int x, int y)    
    4.     {
    5. //Diagz Checks:
    6. //CHECKING LEFT and DOWN...
    7.             if(x - 1 >= 0 && y-1 >= 0 && CoreGameManager.gridBoardCopy[x-1,y-1] != null && CoreGameManager.gridBoardCopy[x-1,y-1].GemColorType != Gem.GEMTYPE.EMPTY)
    8.             {
    9.                 isCheckingNeighbors = true;
    10.                 while (isCheckingNeighbors)
    11.                 {
    12.                     if(x - 1  >= 0 && y-1 >= 0 && CoreGameManager.gridBoardCopy[x-1,y-1] != null && CoreGameManager.gridBoardCopy[x-1,y-1].GemColorType != Gem.GEMTYPE.EMPTY )
    13.                     {
    14.                         TestTile(x-1,y-1);
    15.                         if(isGemMatching == true)
    16.                         {
    17.                                     collector.Add(CoreGameManager.gridBoardCopy[x-1,y-1]);
    18.                                     currentBaseTile = CoreGameManager.gridBoardCopy[x-1,y-1];
    19.                                     CoreGameManager.gridBoardCopy[x-1,y-1] = null;
    20.                                     CoreGameManager.gridBoardCopy[x,y] = null;
    21.                                     diagzMatchCount ++;
    22.                                      x = x-1;
    23.                                      y = y-1;
    24.                         }
    25.                         else
    26.                         {
    27.                             isCheckingNeighbors = false;//Set the LOOP Exit condition.
    28.                         }
    29.                     }
    30.                     else
    31.                     {
    32.                         isCheckingNeighbors = false;
    33.                     }
    34.                 }
    35.                     currentBaseTile = parentBaseTile;
    36.                 x = currentBaseTile.Row;
    37.                 y = currentBaseTile.Column;
    38.             }
    39.        
    40. //CHECKING RIGHT and UP....
    41.             if(x + 1 <= GameBoard.BoardWidth-1 && y+1 <= GameBoard.BoardHeight-1 && CoreGameManager.gridBoardCopy[x+1,y+1] != null && CoreGameManager.gridBoardCopy[x+1,y+1].GemColorType != Gem.GEMTYPE.EMPTY )
    42.             {
    43.                 isCheckingNeighbors = true;
    44.                 while (isCheckingNeighbors)
    45.                 {
    46.                     if(x + 1 <= GameBoard.BoardWidth-1 && y+1 <= GameBoard.BoardHeight-1 && CoreGameManager.gridBoardCopy[x+1,y+1] != null && CoreGameManager.gridBoardCopy[x+1,y+1].GemColorType != Gem.GEMTYPE.EMPTY )
    47.                     {
    48.                             TestTile(x+1,y+1);
    49.                             if(isGemMatching == true)
    50.                             {
    51.                                 collector.Add(CoreGameManager.gridBoardCopy[x+1,y+1]);
    52.                                 currentBaseTile = CoreGameManager.gridBoardCopy[x+1,y+1];
    53.                                 CoreGameManager.gridBoardCopy[x+1,y+1] = null;
    54.                                 CoreGameManager.gridBoardCopy[x,y] = null;
    55.                                  x = x+1;
    56.                                  y = y+1;
    57.                                 diagzMatchCount++;
    58.                             }
    59.                             else
    60.                             {
    61.                                 isCheckingNeighbors = false;//Set the LOOP Exit condition.
    62.                             }
    63.                     }
    64.                     else
    65.                     {
    66.                         isCheckingNeighbors = false;
    67.                     }
    68.                 }
    69.             currentBaseTile = parentBaseTile;
    70.             x = currentBaseTile.Row;
    71.             y = currentBaseTile.Column;
    72.             }
    73.             diagzMatchCount = 0;
    74.  
    75.             //If we Found 3 or more of same color tile in a straight line then, Process collector
    76.             if(collector.Count >= minNumGemsForChain - 1)
    77.             {
    78.                 if(!finalListOfGemsToDestroy.Contains(parentBaseTile))
    79.                 {
    80.                     finalListOfGemsToDestroy.Add (parentBaseTile);
    81.                 }
    82.                     finalListOfGemsToDestroy.AddRange(collector);
    83.             }
    84.             collector.Clear();
    85.  
    86. //CHECKING LEFT and UP
    87.             if(x - 1 >= 0 && y+1 <= GameBoard.BoardHeight-1 && CoreGameManager.gridBoardCopy[x-1,y+1] != null && CoreGameManager.gridBoardCopy[x-1,y+1].GemColorType != Gem.GEMTYPE.EMPTY)
    88.             {
    89.                 isCheckingNeighbors = true;
    90.                 while (isCheckingNeighbors)
    91.                 {
    92.                     if(x - 1 >= 0 && y+1 <= GameBoard.BoardHeight-1 && CoreGameManager.gridBoardCopy[x-1,y+1] != null && CoreGameManager.gridBoardCopy[x-1,y+1].GemColorType != Gem.GEMTYPE.EMPTY )
    93.                     {
    94.                         TestTile(x-1,y+1);
    95.                         if(isGemMatching == true)
    96.                         {
    97.                             collector.Add(CoreGameManager.gridBoardCopy[x-1,y+1]);
    98.                             currentBaseTile = CoreGameManager.gridBoardCopy[x-1,y+1];
    99.                             CoreGameManager.gridBoardCopy[x-1,y+1] = null;
    100.                             CoreGameManager.gridBoardCopy[x,y] = null;
    101.                             diagzMatchCount ++;
    102.                             x = x-1;
    103.                             y = y+1;
    104.                         }
    105.                         else
    106.                         {
    107.                             isCheckingNeighbors = false;//Set the LOOP Exit condition.
    108.                         }
    109.                     }
    110.                     else
    111.                     {
    112.                         isCheckingNeighbors = false;
    113.                     }
    114.                 }
    115.                 currentBaseTile = parentBaseTile;
    116.                 x = currentBaseTile.Row;
    117.                 y = currentBaseTile.Column;
    118.             }
    119.        
    120.        
    121. //CHECKING RIGHT and DOWN....
    122.             if(x + 1 <= GameBoard.BoardWidth-1 && y-1 >= 0 && CoreGameManager.gridBoardCopy[x+1,y-1] != null && CoreGameManager.gridBoardCopy[x+1,y-1].GemColorType != Gem.GEMTYPE.EMPTY)
    123.             {
    124.                 isCheckingNeighbors = true;
    125.                 while (isCheckingNeighbors)
    126.                 {
    127.                     if(x + 1 <= GameBoard.BoardWidth-1 && y-1 >= 0 && CoreGameManager.gridBoardCopy[x+1,y-1] != null && CoreGameManager.gridBoardCopy[x+1,y-1].GemColorType != Gem.GEMTYPE.EMPTY )
    128.                     {
    129.                         TestTile(x+1,y-1);
    130.                         if(isGemMatching == true)
    131.                         {
    132.                             collector.Add(CoreGameManager.gridBoardCopy[x+1,y-1]);
    133.                             currentBaseTile = CoreGameManager.gridBoardCopy[x+1,y-1];
    134.                             CoreGameManager.gridBoardCopy[x+1,y-1] = null;
    135.                             CoreGameManager.gridBoardCopy[x,y] = null;
    136.                             x = x+1;
    137.                             y = y-1;
    138.                             diagzMatchCount++;
    139.                         }
    140.                         else
    141.                         {
    142.                             isCheckingNeighbors = false;//Set the LOOP Exit condition.
    143.                         }
    144.                     }
    145.                     else
    146.                     {
    147.                         isCheckingNeighbors = false;
    148.                     }
    149.                 }
    150.                 currentBaseTile = parentBaseTile;
    151.                 x = currentBaseTile.Row;
    152.                 y = currentBaseTile.Column;
    153.             }
    154.                 diagzMatchCount = 0;
    155.                 //If we Found 3 or more of same color tile in a straight line ,rocess collector
    156.                 if(collector.Count >= minNumGemsForChain - 1)
    157.                 {
    158.                     if(!finalListOfGemsToDestroy.Contains(parentBaseTile))
    159.                     {
    160.                         finalListOfGemsToDestroy.Add (parentBaseTile);
    161.                     }
    162.                         finalListOfGemsToDestroy.AddRange(collector);
    163.                 }
    164.                 collector.Clear ();
    165.     }
    166.    
    167.        

    DESTROY FINAL LIST:
    Code (CSharp):
    1.    
    2.    
    3. //Destroy FINAL list of contiguous matching gems.  Send the LIST to destroy INTO the helper method
    4. public static void DestroyMatchingGems(List<Gem> matchingListOfContiguousGemsToRemove)
    5.     {
    6.         //Go through each gem in our Final compiled list of gems to be destroyed
    7.         for(int i=0; i < matchingListOfContiguousGemsToRemove.Count;i++)
    8.         {
    9.             int x = matchingListOfContiguousGemsToRemove[i].Row;
    10.             int y = matchingListOfContiguousGemsToRemove[i].Column;
    11.             Destroy (CoreGameManager.visualGrid[x,y]);
    12.             //Populate gridtile with a NEW EMPTY, NORMAL GEM
    13.             CoreGameManager.gridBoard[x,y] = new Gem(Gem.GEMTYPE.EMPTY,Gem.GEMMOD.NORMAL,x,y);
    14.             //Visual Grid array NULL is "empty"
    15.             CoreGameManager.visualGrid[x,y] = null;
    16.            
    17.         }
    18.             //Clean Up
    19.             matchingListOfContiguousGemsToRemove.Clear();
    20.     }
    21.    
    RESETTLE BOARD:

    Code (CSharp):
    1.  
    2. /*
    3. * In each row, we’ll work our way up from the bottom until we find an empty cell.
    4. * Then, we’ll make a note of that cell. The next time we find a tile,
    5. * we’ll simply shift it down to that location and add one to our “empty cell” index:
    6. */
    7. public static void SettleBlocks(GameBoard board)
    8.         {
    9.             //Clear the List as we are about to use/re-use it
    10.             topOfRecursionBaseTiles.Clear ();
    11.             for (int x = 0; x < GameBoard.BoardWidth; x++)
    12.             {
    13.                 firstEmptyTile = null;
    14.            
    15.                 for (int y = 0; y <GameBoard.BoardHeight; y++)
    16.                 {
    17.                     //check if the tile is EMPTY and firstEmptyTile is NULL
    18.                     if(board[x,y].GemColorType == Gem.GEMTYPE.EMPTY && !firstEmptyTile.HasValue )
    19.                     {
    20.                         firstEmptyTile = y;
    21.                     }
    22.                     else if(board[x,y].GemColorType != Gem.GEMTYPE.EMPTY && firstEmptyTile.HasValue)
    23.                     {
    24.                         CoreGameManager.visualGrid[x,firstEmptyTile.Value] = CoreGameManager.visualGrid[x,y];
    25.                         board[x,firstEmptyTile.Value] = board[x, y];                    
    26.                         board[x, y] = new Gem (Gem.GEMTYPE.EMPTY,Gem.GEMMOD.NORMAL,x,y);
    27.                         //UPDATE INDEX POINTER COORDS IN THE "GEM" at BOARD LOCATION
    28.                         //X will always be the same as we dont move Gems left to right when we resettle the board
    29.                         board[x, firstEmptyTile.Value].Row = x;
    30.                         //Y Needs to be updated as we moved it DOWN "firstEmptyTile.Value" number of squares
    31.                         board[x, firstEmptyTile.Value].Column = firstEmptyTile.Value;//the NEW Y pos
    32.                         //Add the gems ABOVE the empty gaps as ROOT base tiles since we updated their positions internally correctly
    33.                         topOfRecursionBaseTiles.Add(board[x, firstEmptyTile.Value]);
    34.                         //Get the current x,y transform (of the gem GAMEOBJECT) so we can change it
    35.                         Vector3    currentGemToRestack = CoreGameManager.visualGrid[x,firstEmptyTile.Value].transform.position;
    36.                         //Save the Value as the "New" Y position to use
    37.                         currentGemToRestack.y = (firstEmptyTile.Value);
    38.                         //Move the gem down
    39.                         CoreGameManager.visualGrid[x,firstEmptyTile.Value].transform.position = currentGemToRestack;
    40.                         //We moved the gem down, now make sure the empty gap index is updated
    41.                         firstEmptyTile++;
    42.                         //Make the x,y gamebject in memory NULL as we moved it (null is "empty" in the visualGrid Array of GameObjects)
    43.                         CoreGameManager.visualGrid[x,y] = null;
    44.                     }
    45.                 }
    46.        
    47.             }
    48.  
    49. //Testing
    50.         for(int i =0;i <topOfRecursionBaseTiles.Count;i++)
    51.             {
    52. print ("topOfRecursionBaseTiles: "+topOfRecursionBaseTiles[i]);
    53.             }
    54.        
    55.         }
    Sorry for the LONG post, but I figured I would post the code BEFORE someone says "hey, we dont know what the hell your talking about without seeing it..." Lol
     

    Attached Files:

    Last edited: Dec 18, 2014
  16. R-Lindsay

    R-Lindsay

    Joined:
    Aug 9, 2014
    Posts:
    287
    I'm probably not going to get a chance to look through all this code, but now that you've written it, I recommend you go back and simplify it. You have an idea about how you want to tackle your problem so the second time through should be much faster and allow you to drastically reduce the amount of code needed. For example, your "checking left" and "checking right" code is virtually identical (same for checking left&up, and right&down) and could probably be the just a single block of code. In fact, you could factor out a lot of your code into 'checking adjacent' styles functions that only varies by how the x & y values change. E.g. in any direction you can define a 'PreviousGem' and 'NextGem', where the value returned is determined by the the axis - horizontal, vertical, or diagonal.

    R
     
  17. angrypenguin

    angrypenguin

    Joined:
    Dec 29, 2011
    Posts:
    15,617
    Yeah, that's a lot of code.
     
  18. KingTooTall

    KingTooTall

    Joined:
    Oct 17, 2014
    Posts:
    61
    Ok, I see the point about alot of code, and I have once before gone through it and broke it down, as I will do again as suggested. I am sorry for the post, I have no one helping me but you guys, so I am giving it a go...Alone.. lol
    The code is mostly repeative , so it should be to hard to break it down further, however, its NOT working, so I dont know if i should start over or just keep on trying to debug this mess? lol Either way, I'm sorry to tell you guys, I just cant give this up until i get it right. ha Since im not obviously grasping what R.Lindsay and super angry (at me? lol) penguin are trying to tell me to write, maybe a little routine (code) to suggest what direction i should be going other then my previous approach? :)
    Thanks
    King

    Great..I can hear it now... King Too (much code) Tall...
     
  19. R-Lindsay

    R-Lindsay

    Joined:
    Aug 9, 2014
    Posts:
    287
    There are countless ways to refactor this. Here are some suggestions.

    First up let me just say it's fine to break down the problem into small steps as you have tried to do. However what we want to do after that is factor out common logical steps that are being repeated into a single block of code or function.
    For example a lot of your code is concerned with moving along a given axis - horizontal, vertical, negative diagonal and positive diagonal - and collecting a match. All of this code has a common set of steps: given a particular gem, collect the matching gems before and after it into a match sequence. If the sequence length is over a given value, say 3, save them for later processing (in this case deletion).

    If you look over your code, this logic is repeated numerous times for each axis. Not only that, its repeated two times for each axis - when checking previous and next gem matches.But the worst thing of all is that it is not done in a way that we can reuse. The next time you have to move around the board, you have to write everything again from scratch.

    Before I offer some psuedocode let me highlight what I just said. Whenever you pass around a gem you are also passing around - separately - it's x & y values. E.g.
    Code (CSharp):
    1. public static void CheckForNeighborsVert(Gem currentBaseTile, int x, int y)
    This is ugly. Navigating around the board is going to be something you do a lot. We don't want to have to write it out (and debug it) 500 times. To make our code more readable we just want to pass around gems and refer to those Gems adjacent to it without specifying x&y values. We would prefer code such that given a gem we can always retrieve it's position on the board. We want to be able to find gems based on their relation to one another. One way is to add the following methods to the Gem class (these are a spatial relation):
    Code (CSharp):
    1. public Gem Above();
    2. public Gem Below();
    3. public Gem Left();
    4. public Gem Right();
    In order to do this gems will need a method to find their position. You can either store the x&y values on each gem instance, and remember to keep it updated as they move, or index each gem location on the board class with a hashtable. Or just search for it each time.
    The methods should return either a Gem or null if there is no adjacent Gem in that direction. You can call these methods whatever you like, I just used Above(), Below() etc as an example.

    Building on this, we can specify our axis and add in a function to easily navigate along a given axis (a slightly higher level of spatial abstraction). Implementing the following two methods is one such way to do this:
    Code (CSharp):
    1.         public Enum AXIS
    2.         {
    3.             HORIZONTAL,
    4.             VERTICAL,
    5.             DIAGONAL_POS,
    6.             DIAGONAL_NEG
    7.         }
    8.  
    9.         public Gem Previous(AXIS axis)
    10.         {
    11.  
    12.         }
    13.  
    14.         public Gem Next(AXIS axis)
    15.         {
    16.  
    17.         }
    Finally, we can replace almost all of the code you wrote above with the following code that is vastly simpler. Best of all we can reuse the navigational code in other functions.
    Code (CSharp):
    1.         public static List<Gem> Match(Gem gem, AXIS axis, int minMatchLength)
    2.         {
    3.             List<Gem> result = new List<Gem>();
    4.      
    5.             // add the given gem
    6.             result.Add(Gem);
    7.  
    8.             // find longest common match before our given gem
    9.             Gem check = gem;
    10.             while ((check = check.Previous(axis)) != null)
    11.             {
    12.                 if (gem.Matches(check))
    13.                 {
    14.                     // Prepend the matched gem
    15.                     result.Insert(0, check);
    16.                 }
    17.                 else
    18.                 {
    19.                     break;
    20.                 }
    21.             }
    22.  
    23.             // find longest common match after out given gem
    24.             check = gem;
    25.             while ((check = check.Next(axis)) != null)
    26.             {
    27.                 if (gem.Matches(check))
    28.                 {
    29.                     // Append the matched gem
    30.                     result.Add(check);
    31.                 }
    32.                 else
    33.                 {
    34.                     break;
    35.                 }
    36.             }
    37.  
    38.             return result.Count >= minMatchLength ? result : null;
    39.         }
    Why is this an improvement...
    It's important to reflect and really understand why this is an improvement on the code you have (remember, this is pseudocode and almost certainly wont compile). We have factored out all the code that navigates around the board, and our matching function above has only one simple task: using the navigational methods specified on the Gem class we do only one thing - build up match sequences.
    You will have a lot of code in your game that navigates around the board in various ways, based on various relationships between gems (in this case the relationships are spatial) and creating robust methods to do this easily is going to vastly reduce the amount of time spent writing and debugging. Also it is far clearer to read and understand what is going on.

    Like I said, this is only one way to approach the problem. At the risk of sounding like a broken record please remember that the important lesson to take away is to factor out common logic - in this case finding gems in a particular relation to another - into general methods that make our code much easier to read and debug, and allow us to build complexity upon them without drowning in unreadable code.

    R

    P.S. @CaptainScience looks like he was telling you something very similar to this, though with a different implementation (possibly better than mine). Remember the specific implementation I've presented is less important than the lesson - break down problems into small reusable methods that can be layered to build up complexity in a natural way. Think about the relations between objects that are important in your game and write out methods to allow you to work with them naturally.
     
    Last edited: Dec 20, 2014
  20. KingTooTall

    KingTooTall

    Joined:
    Oct 17, 2014
    Posts:
    61
    R.Lindsay, thank you for the reply again.The answer that you have given above is to the question I was trying to ask (but didn't know how to word?) in the first place. I knew there was going to be a good way and a not so good way to setup my data structures/classes. I have approached the resolution to my problem the brute force way, lol which is the not so smart way. Let me go take another look at how my Gem class is setup, and implement the methods you described.
    I'll follow up with my progress asap. Thanks Again. And yes, I did take away more then just the simple code you put together from this. I knew the WHAT, just not the HOW.
    King
     
  21. angrypenguin

    angrypenguin

    Joined:
    Dec 29, 2011
    Posts:
    15,617
    I'm not angry at you. ;) I think your approach is making this more complex than it has to be, though, and that's coming from someone who implemented more or less exactly this only a few weeks ago.

    My basic structure is this. I've got a "Board", which is just a grid (2D array) of positions each of which can have a gem. The board does the following three things:
    a) Maintain a 2D array of which gems are in each grid cell.
    b) Allow for swapping of gems between two specified grid cells.
    c) Tell a bunch of separate objects, each of which represent a subset of the rules of the game, to do their thing.

    C is important, because that's where this is split into a bunch of small, really simple problems. Each module only has to understand what it does, and only has to be aware of the board to do its thing. After the player moves something, the board asks the relevant object to see if there are any matches. If there are, it asks another object to remove them from the board. If anything is removed, it asks another to re-shuffle the board and then yet another to fill in any resulting gaps. That sequence is repeated until no matches are found.

    What's neat about that is that each of the objects that implement a rule only have to do one really simple job. With access to the grid contents via the board, it's pretty much trivial for the match detector to find matches. (It's two simple loops and a counter. That's all.) Once matches have been identified it's even easier for the object that removes stuff from the board to do its thing. (In fact, 95% of the code in that object is for animation.) And so on.

    Don't try and solve big problems all in one go. Break them down into their parts. See if there's a simple way you can represent your data that makes those parts easy to solve. Then, depending on how you want your software to work, split it up over a bunch of methods, classes, phases, or whatever suits and solve them one at a time. Test each part as you go, because that's easier than testing the whole solution at once (eg: it's easier to test that match detection works and then build on it than it is to build everything and then test that the whole solution works).

    (As a bonus, this approach also makes it really easy to implement different rules for each stage. For instance, if I wanted to match shapes instead of lines I'd only have to write one new method and swap one component out for another. Nothing else would care.)
     
  22. Random_Civilian

    Random_Civilian

    Joined:
    Nov 5, 2014
    Posts:
    55
    Warning: lightly skimmed over the posts so apologies in advance.

    Well, 2d arrays are a good choice for this. However if you want to micro (?) optimize or expand to a larger grid, I would recommend using hashsets per color and store the coords of the gems.
     
  23. angrypenguin

    angrypenguin

    Joined:
    Dec 29, 2011
    Posts:
    15,617
    What algorithm would be improved by use of such a data structure? You need to iterate over gems based on their location, not their colour.
     
  24. Random_Civilian

    Random_Civilian

    Joined:
    Nov 5, 2014
    Posts:
    55
    Well, the op was looking for patterns like a line. A simple if contains statement for neighbors of the desired color would suffice, along with an additional hashset to keep track of checked coordinates

    Edit: additional sub optimization. If neighbors not of the pattern do not matter, you only need to check all neighbors of the center tile of the pattern. That should determine the direction of where you want to search next
     
    Last edited: Dec 22, 2014
  25. angrypenguin

    angrypenguin

    Joined:
    Dec 29, 2011
    Posts:
    15,617
    Lets not worry about optimization until they've got something that works.

    But also, I wouldn't worry about that type of "optimization". The un-optimized version is looking at a grid location to see what it contains. Job done. The "optimized" version starts by looking at a hash set to see if it's already looked at a grid location... which I expect will be more expensive than just looking at the grid location again.
     
    Random_Civilian likes this.
  26. Random_Civilian

    Random_Civilian

    Joined:
    Nov 5, 2014
    Posts:
    55
    Fair enough :)
     
  27. R-Lindsay

    R-Lindsay

    Joined:
    Aug 9, 2014
    Posts:
    287
    This is the essence of pretty much every substantial post in this thread. If the op can internalise this and apply it he will have taken away something more valuable than just debugging an overly complex piece of gem matching code.
     
    superpig and angrypenguin like this.
  28. KingTooTall

    KingTooTall

    Joined:
    Oct 17, 2014
    Posts:
    61
    Hey hey!
    Thanks for all the replys! I've had to spend some time with my family this weekend as the holidays are here, so I did not get a chance to code anything yet. I do however want to comment on a few things...

    Just from this post alone I have learned ALOT more then just A "proper" way to write the match making routines. In the beginning What I was looking for was , well, for a few things and not just a "oh here is how you fix that, or do this" type of answer. I knew that I wanted to recreate the puzzle game Columns made by SEGA,, not using any of the asset store tools (which there are some good ones out there I'm sure.). Programming the "CORE" and the rest of the game myself *EDIT: OR with ANYONE who wants to help BTW! Feel free to just message me,or post here and ill be happy to share what I have as a collaboration effort on the forums, maybe some of us can learn together?). I knew there would be a (generalized term here) "proper" way to go about setting this up or arranging my data in memory BEFORE I even started coding so that in the "end", the goal was (for this portion of the problem) to make it as easy as possible to be able to do the following:
    Make a Board of "some type" (int, words, custom class?) that was a 2D grid that could contain everything i needed, to make the logic of matching colors of adjacent tiles of 3 or more, in a straight line,in any of the 8 directions, as easy and as fast as possible (the core rules of the game eh?).

    I thought the best way to set this up (initially) was simply to be able to "ask" the board what was at position X,Y, what color it was, then, based on what the board reported back, DO SOMETHING...(like count the matches, test IF the gems match" etc.),
    Well, thats not incorrect, but the problem comes in at the part where I told and asked myself, now that I know that "WHAT im trying to do..", "HOW the hell do I (structure ) do what I just described, in an efficient manor." lol. Since I'm not one to "run" right to the forums and starting asking essentially, "how do I make this game" type questions, I tried to break it down into (Spoiler Alert: I'm NOT new to programming, but new to programming in a correct fashion, rather then hacking something together? :) ) something smaller (based on my knowledge of C# and the MOD we wrote for Call of Duty modern warfare, (yes thats another story all together, but interesting how its related to my learning of unity, and C#..we'll save that for another day.)) so I could accomplish the core mechanics of the game BEFORE I started to code the "pretty" things or anything beyond the basics.
    With out the "bigger" picture in mind, I thought the best way to go about setting the core rules up was (yeah, yeah, laugh at this noobish move) to just use a 2D Array of Integer's for the grid, and hold the visual gem gameobjects in a LIST, then when the gem dropping box, hit the bottom of the grid or a another gem below, "brute Force" the board check AND the attatching GameObject List , getting the x,y coords and color from each gameobject and testing them, moving on, testing, etc. process the lists, etc....and as R.Lindsay stated in my other post, it did just what he already knew was going to happen: SPAGHETTI CODE CITY! (and it was to slow to use lol) Several attempts later, I obviously needed/NEED some help with this, so I posted here. I have learned some very important things in just a short time, and NOT just about my needs for this situation.

    I now know how to:
    - Better arrange and design my assets in a manner that will "fit" what im trying to do.
    - Make a custom class type instead of limiting myself to something to small for a bigger problem (integer array vs a more robust custom class type)
    - Access properities of each custom class type (uning Enum's)
    - Compartmentalize my functions to better manager them and also for reusing them for repeatitive tasks.

    So yes, THANKS! :) I wil lhave some time this week, so I will get back here when I code up what we have discussed up to this point, I will be trying to APPLY the method theorys we have discussed.

    King
     
    angrypenguin likes this.
  29. angrypenguin

    angrypenguin

    Joined:
    Dec 29, 2011
    Posts:
    15,617
    I'm really happy you got so much out of our help! Seeing people learn like that is actually what I like about helping out.

    Yes, custom classes are a great tool for breaking a problem down into small, manageable components. Same deal goes for data structures. The default ones available in .NET (check out the System.Collections.Generic namespace) will cover you for the vast majority of things (so get familiar with them and their pros and cons - they're all useful, but only if you use the right one for the job), but every so often you'll run into something where either adding to one of those or making your own helps.

    What do you mean by this?
     
  30. KingTooTall

    KingTooTall

    Joined:
    Oct 17, 2014
    Posts:
    61
    Penguin,
    KingTooTall said:
    - Access properities of each custom class type (uning Enum's), is a stupid typo, it was supposed to be (USING ENUMS), e.g. I use enums to hold my gem colors, gem prefabs, amongst other things, like power ups, different gem "kinds" etc.

    Work In progress...
    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3. using System.Collections.Generic;
    4.  
    5.  
    6.  
    7. namespace GemTitan
    8. {
    9. public class Gem
    10. {
    11.        
    12. #region Variables
    13.     public GEMTYPE GemColorType;
    14.     public GEMMOD GemMod;
    15.     public GEMPREFABS GemPrefabMaterialName;
    16.    
    17.  
    18. //Gem.maxNumOfGems contains the MAX num of different type gems we have to use.One of the gems(Zero) is used as EMPTY
    19. public static int maxNumOfGems = (int)(System.Enum.GetNames(typeof(GEMTYPE)).Length);
    20. //Gem.maxNumOfGems contains the MAX num of different type gems we have to use.One of the gems(Zero) is used as EMPTY
    21. public static int maxNumOfMods = (int)(System.Enum.GetNames(typeof(GEMMOD)).Length);
    22. //MAX num of different type Materials we have to use
    23. public static int maxNumOfMaterials = (int)(System.Enum.GetNames(typeof(GEMPREFABS)).Length);
    24. //Stores the correct Gem material in this variable from Gem Class (used for our material color compare later)
    25. public static string gemMat = null;
    26. public static int maxSizeOfGemDroppingBoxList = 9; // 3 sets of 3 gems each = 9
    27.  
    28. public int Row; //X
    29. public int Column; //Y
    30.  
    31.        
    32. #endregion
    33.        
    34. #region ENUMERATIONS
    35. public enum GEMTYPE
    36.         {
    37.             //ZERO is RESERVED for an EMPTY tile/gem type check
    38.             EMPTY    = 0,
    39.             RED        = 1,
    40.             GREEN    = 2,
    41.             BLUE    = 3,
    42.             ORANGE    = 4,
    43.             YELLOW    = 5,
    44.             PURPLE    = 6,
    45.             RANDOM     = 7// Used for a place holder when generating a RANDOM gem for the dropping gem box MUST BE LAST ENTRY!
    46.            
    47.         }
    48.    
    49. public enum GEMMOD
    50.         {
    51.             //ZERO is RESERVED
    52.             EMPTY        = 0,
    53.             NORMAL         = 1,
    54.             //"Goal" gem. i.e. first player to get to their flashy gem and make it match 3 of same color as the flashy gem, then SOME TRIGGER..(game over? next level? etc)
    55.             FLASHING    = 2,
    56.             //When bottom gem of the 3 gem dropping box hits a gem below depending one WHAT gem of the 3 shape ("3 cell diamond")hit, this will do various things
    57.             SPECIAL        = 3,
    58.             //Do Random things to the OTHER player if used (flip their grid upside down, or make all their gem colors gray, etc.
    59.             POWERUP        = 4,
    60.             //RANDOM MUST ALWAYS BE LAST!(Fix needed so it doesnt matter where RANDOM is..)
    61.             RANDOM         = 5//// Used for a place holder when generating a RANDOM MOD for the dropping gem box
    62.         }
    63.        
    64. public enum GEMPREFABS
    65.     {
    66.         Gem_Empty    = GEMTYPE.EMPTY,
    67.         Gem_Red        = GEMTYPE.RED,
    68.         Gem_Green    = GEMTYPE.GREEN,
    69.         Gem_Blue    = GEMTYPE.BLUE,
    70.         Gem_Orange    = GEMTYPE.ORANGE,
    71.         Gem_Yellow    = GEMTYPE.YELLOW,
    72.         Gem_Purple    = GEMTYPE.PURPLE
    73.    
    74.     }
    75.  
    76. public enum AXIS
    77.     {
    78.     HORIZONTAL,
    79.     VERTICAL,
    80.     DIAGONAL_POS,
    81.     DIAGONAL_NEG
    82.     }
    83.        
    84. #endregion
    85.        
    86.        
    87. public Gem(GEMTYPE gemColor, GEMMOD gemMod,int x, int y)
    88.         {
    89.             GemColorType = gemColor;
    90.             GemMod = gemMod;
    91.             Row = x;
    92.             Column = y;
    93.         }
    94.        
    95. //Helper method to Match the correct Prefab to the Gem color being created.(e.g RED gem = prefab/Gem_Red)
    96. public static void GetGemPrefabName(GEMTYPE gemColor)
    97.     {
    98.         switch(gemColor)
    99.         {
    100.             default:
    101.             gemMat = "Gem_Empty";
    102.             return;
    103.             case GEMTYPE.EMPTY:
    104.             gemMat = "Gem_Empty";
    105.             return;
    106.             case GEMTYPE.RED:
    107.             gemMat = "Gem_Red";
    108.             return;
    109.             case GEMTYPE.GREEN:
    110.             gemMat = "Gem_Green";
    111.             return;
    112.             case GEMTYPE.BLUE:
    113.             gemMat = "Gem_Blue";
    114.             return;
    115.             case GEMTYPE.ORANGE:
    116.             gemMat = "Gem_Orange";
    117.             return;
    118.             case GEMTYPE.YELLOW:
    119.             gemMat = "Gem_Yellow";
    120.             return;
    121.             case GEMTYPE.PURPLE:
    122.             gemMat = "Gem_Purple";
    123.             return;
    124.         }
    125.     }
    126.  
    127.  
    128.  
    129. ..To be continued.. :)
    Yeah, please dont judge the above code to harshly, I'm figuring this out as I go. Lol.

    Yes, Penguin, when I first made a go at this match making algorithm using just game objects, I learned how to use LISTS VERY quickly Lol. They are a great feature, and dont seem to be too slow for most things I would use them for thus far, and they better helped me understand or connect the dots if you will, with the use of ARRAYS, and my custom class. When I first learned this little feature, I was like "Whaaaat?!?! you can make your OWN <TYPE> Class and still use all the "commands" available to built in arrays?!?!" hell yeah.(e.g SomeList.Add, SomeList.Count etc..) That alone led me to believe that I could write this game a number of different ways. Lol

    Thanks
    King
     
  31. angrypenguin

    angrypenguin

    Joined:
    Dec 29, 2011
    Posts:
    15,617
    I know it was meant to be "using". I just wasn't sure what you meant, as you "use enums" to "access properties". If you simply mean that you're accessing properties which are enums, cool - though I say that assuming you're aware that you can access anything in the same manner if it's publicly scoped.

    I notice you're using "static" up there. Be careful with that, it leads plenty of inexperienced people into a lot of trouble. My eye was particularly drawn to a static material reference you've got there, but anything static causes me to raise my eyebrows and ask "is that really necessary?".
     
  32. zombiegorilla

    zombiegorilla

    Moderator

    Joined:
    May 8, 2012
    Posts:
    9,042
    Been on vacation, otherwise I would have weighed in sooner. I have done a ton of these types of games, and share my general approach. Mainly, I try to keep the logic very simple, that allows for fewer edge cases and makes it easy to expand for more complex features.

    To start, in this thread I show an example and provide the overall logic flow:
    http://forum.unity3d.com/threads/grid-game-best-way-to-start.258715/

    In its stripped down basic form (excluding UI/core game/extra features) I have use the following classes
    Gem (extends AnimatedGem which contains animations and board movement)
    - contains all information pertaining to each gem
    Board (extends interactions)
    - contains rules and general board interactions and structure
    SlideLogic (extends Logic)
    - contains all the logic for matching and back fill gem selection AI.
    - this version is for a sliding match game. I have others for swapping (SwapLogic) and drag select (DragMatchLogic) depending on game style.

    The Board contains an array of gems Gem[,]. When the user takes an action, (swap, slide, etc..) a temp array is created of the order and passed to Logic for validation. (there are also methods in Board for the visualization of actions).

    Logic.parseBoard(gems) returns an array of matched sets. If the array is empty, (depending on game type) it will swap/slide back visually. If not, it will replace the main gem array with temp one (as swaps were valid), and do the removal of the matched sets (visuals, scoring and nulling them in the array).

    I will then compress the board (shifting down or whatever), have the Logic AI determine and fill with new gems based on various factors. Parse it again, and repeat until the matched sets are null and return control to the user/board.

    The matching in its simplest version looks pretty much like this: (stripped down for core functionality)
    Code (CSharp):
    1.  
    2.  
    3.     public Gem[][] parseBoard(Gem[,] gems)
    4.     {
    5.         List<Gem[]> _kill_sets = new List<Gem[]>();
    6.         List<Gem> _check_set = new List<Gem>();
    7.         int cnt = 0;
    8.         int cols = Config.Cols;
    9.         int rows = Config.Rows;
    10.        
    11.         for (int c = 0;c<cols;c++)
    12.         {
    13.             for (int r = 0;r<rows;r++)
    14.             {
    15.                 if(!gems[c,r].killMarked)
    16.                 {
    17.                     // clear check vals/conts
    18.                     _check_set = new List<Gem>();
    19.                     cnt = 1;
    20.                     _check_set.Add(gems[c,r]);  // add the first gem
    21.                    
    22.                     // do cols first
    23.                     while((c+cnt<cols) && !gems[c+cnt,r].killMarked && (gems[c,r].gem_type == gems[c+cnt,r].gem_type))
    24.                     {
    25.                         _check_set.Add(gems[c+cnt,r]);
    26.                         cnt++;
    27.                     }
    28.                    
    29.                     // if the check set count is less than the base conditions, try rows
    30.                     if(_check_set.Count<Config.GameMatchCond)
    31.                     {
    32.                         // clear check vals/conts
    33.                         _check_set = new List<Gem>(); cnt = 1;
    34.                         _check_set.Add(gems[c,r]);  // add the first gem
    35.                    
    36.                         // check rows
    37.                         while((r+cnt<rows) && !gems[c,r+cnt].killMarked && (gems[c,r].gem_type == gems[c,r+cnt].gem_type))
    38.                         {
    39.                             _check_set.Add(gems[c,r+cnt]);  
    40.                             cnt++;
    41.                         }
    42.                     }
    43.                    
    44.                     // if either resulted in valid match set, push them into the kill sets
    45.                     if(_check_set.Count>=Config.GameMatchCond)
    46.                     {
    47.                         int cs=0;
    48.                         Gem[] pull_set = new Gem[_check_set.Count];
    49.                         foreach (Gem g in _check_set)
    50.                         {
    51.                             g.killMarked = true;
    52.                             pull_set[cs] = gems[g.col,g.row];
    53.                             cs++;
    54.                         }
    55.                         _kill_sets.Add(pull_set);
    56.                     }
    57.                 }
    58.             }
    59.         }
    60.         return _kill_sets.ToArray();
    61.     }
    62.  
    This loop is for basic row and col matches without contiguous grouping (row matches have priority). Basically it starts at the top, seeks left for matches, if the total matched gems are at least the required count, it adds the array to the set array and marks them as killed. if row fails, it seeks down. then moves to the next un-killed gem and starts again. It fails out early as possible to keep it efficient. Then returns results.

    Again, this is just the simplest form, I have variations and wrapper functions for added complexity, but I start with the basic form first. Variations include hintCheck which finds potential matches to show hints, and methods for combined matches and ones that handle special gems.

    Hope this helps.
    ZG
     
  33. KingTooTall

    KingTooTall

    Joined:
    Oct 17, 2014
    Posts:
    61
    ZG, its about time you stompped in! lol I have read that post a few times before. (thanks). I am seeing that little sparkley light at the end of the tunnel here, as after re-reading our discussion, ALL of you guys that have been much help are saying the same thing, or suggesting the "same" direction to approach this challenge. You all have answered what I was asking for sure up to this point. I now have to go actually turn the lesson into code that fits this situation, which I will be doing today most likely. I may go back and refine some of my other routines, either way, its SNOWING here up north. lol so i will be inside all day... Anyone want coffee? lol

    Thanks
    King
     
  34. zombiegorilla

    zombiegorilla

    Moderator

    Joined:
    May 8, 2012
    Posts:
    9,042
    How far up north?
    I am up "north" for the Holidays, but only relatively speaking (North of Cali in Oregon), so I am just getting rain... no snow. ;)

    Everyone works different. There are some great replies here. Personally, I start with functionality. Simple hard-coding, editor links and very simplified logic and just a few classes and lots of OnGUI buttons to test things. Once my logic works, I start breaking things up and making it more robust and flexible. I like to know that my core mechanics behave and by limiting the variables I can quickly trouble-shoot. But that is just my process. Some of friends work completely the opposite, they stub out the whole (or most) of the methods/classes/framework first and then plug in functionality. Both work, both have their ups/downs (my approach means constant refactoring). Just depends on what works for you. It's also valuable to try different approaches to hone your own methods.

    Good Luck!
     
  35. KingTooTall

    KingTooTall

    Joined:
    Oct 17, 2014
    Posts:
    61
    ZG, thanks for the input. I work BOTH ways actually. The project goal kind of decides for me what method I use, either stubbing, or on the fly. I'm in Wisconsin, had a WHITE xmas... I lived in Portland for a few years, LOTS of rain..dreary rain... I partied hardy for the holidays, now my stupid b-day is coming up here this weekend, so I thought I'd take a few days and go back over my code/classes and re-do them a little. I will either start a NEW POST with the CODE, so we can discuss THAT, since my main question to this post has already been answered. :) I will return with a new link or update in a few. Going to start with the GEM class and the BOARD, After you guys weigh in on that, I'll go from there.
    600+ views of this post, maybe it was my crafty ( but incorrect? ) subject Title... *Grin*
    Thanks,
    King
     
  36. KingTooTall

    KingTooTall

    Joined:
    Oct 17, 2014
    Posts:
    61
    For the sake of making this post easier to follow, I took a lot of the pseudo code we talked about and kept the variable names and what not you guys have posted in your example code the same or similar.

    This project is (for now) named "GemTitan".

    Here are the Gem and Board Classes:

    Gem.cs
    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3. using System.Collections.Generic;
    4.  
    5. namespace GemTitan
    6. {
    7. public class Gem
    8. {
    9.        
    10. #region Variables
    11.     public GEMTYPE GemColorType;
    12.     public GEMMOD GemMod;
    13.     public GEMPREFABS GemPrefabName;
    14.     public int Row; //X
    15.     public int Col; //Y
    16. #endregion
    17.        
    18. #region ENUMERATIONS
    19. public enum GEMTYPE
    20.     {
    21.         EMPTY = 0,
    22.         RED,
    23.         GREEN,
    24.         BLUE,
    25.         ORANGE,
    26.         YELLOW,
    27.         PURPLE,
    28.         RANDOM    
    29.     }
    30.    
    31. public enum GEMMOD
    32.     {
    33.         EMPTY = 0,
    34.         //Normal gem - no special attrib's
    35.         NORMAL,
    36.         //Goal gem - First player to destroy flashy gem(game over? next level? etc)
    37.         FLASHING,
    38.         //Depending one WHAT special gem shape hit, this will do various things (raise opponent stack?Destroy all of a certain color gem on the players board? etc)
    39.         SPECIAL,
    40.         //Do Random offensive things to the OTHER player if used (flip their grid upside down? or make all their gem colors grey? etc)
    41.         POWERUP,
    42.         RANDOM
    43.     }
    44.        
    45. public enum GEMPREFABS
    46.     {
    47.         Gem_Empty,
    48.         Gem_Red,
    49.         Gem_Green,
    50.         Gem_Blue,
    51.         Gem_Orange,
    52.         Gem_Yellow,
    53.         Gem_Purple
    54.     }
    55.  
    56. public enum AXIS
    57.     {
    58.         HORIZONTAL,
    59.         VERTICAL,
    60.         DIAGONAL_POS,
    61.         DIAGONAL_NEG
    62.     }
    63. #endregion
    64.        
    65.        
    66. public Gem(GEMTYPE gemColor, GEMMOD gemMod,int x, int y)
    67.         {
    68.             GemColorType = gemColor;
    69.             GemMod = gemMod;
    70.             Row = x;
    71.             Col = y;
    72.             GemPrefabName = GetGemPrefabName(gemColor);
    73.             //CONVERT prefab name to a string below? NOT TESTED!
    74.             //string GemPrefabName = GetGemPrefabName(gemColor).ToString();
    75.         }
    76.        
    77. //Helper method to Match the correct Prefab to the Gem color being created.(e.g RED gem = prefab/Gem_Red)
    78. public GEMPREFABS GetGemPrefabName(GEMTYPE gemColor)
    79.     {
    80.         switch(gemColor)
    81.         {
    82.             default:
    83.             return GEMPREFABS.Gem_Empty;
    84.             case GEMTYPE.EMPTY:
    85.             return GEMPREFABS.Gem_Empty;
    86.             case GEMTYPE.RED:
    87.             return GEMPREFABS.Gem_Red;
    88.             case GEMTYPE.GREEN:
    89.             return GEMPREFABS.Gem_Green;
    90.             case GEMTYPE.BLUE:
    91.             return GEMPREFABS.Gem_Blue;
    92.             case GEMTYPE.ORANGE:
    93.             return GEMPREFABS.Gem_Orange;
    94.             case GEMTYPE.YELLOW:
    95.             return GEMPREFABS.Gem_Yellow;
    96.             case GEMTYPE.PURPLE:
    97.             return GEMPREFABS.Gem_Purple;
    98.         }
    99.     }  
    100.  
    101. //MATCH MAKING OF ADJACENT GEMS - Find Adjacent Same Color Gems or return NULL
    102. public Gem FindGemAbove()
    103. {
    104.     //Make sure we are inside the boundries of the grid and the tile is NOT empty
    105.     if(Col + 1 <= GameBoard.BoardHeight && CoreGameManager.gridBoard[Row,Col + 1].GemColorType != Gem.GEMTYPE.EMPTY)
    106.             {
    107.                 return CoreGameManager.gridBoard[Row,Col + 1];
    108.             }
    109.     //We are outside of grid OR the tile is EMPTY
    110.     return null;
    111. }
    112.  
    113. public Gem FindGemBelow()
    114. {
    115.     if(Col - 1 >= 0 && CoreGameManager.gridBoard[Row,Col - 1].GemColorType != Gem.GEMTYPE.EMPTY)
    116.             {
    117.                 return CoreGameManager.gridBoard[Row,Col - 1];
    118.             }
    119.     return null;
    120. }
    121.        
    122. public Gem Previous(AXIS axis)
    123. {
    124.     switch(axis)
    125.     {
    126.         case AXIS.VERTICAL:
    127.         return FindGemAbove();
    128.     }
    129.             return null;
    130. }
    131.  
    132. public Gem Next(AXIS axis)
    133. {
    134.     switch(axis)
    135.     {
    136.         case AXIS.VERTICAL:
    137.         return FindGemBelow();
    138.     }
    139.         return null;
    140. }
    141.        
    142. public bool Matches(Gem gem)
    143.     {
    144.         //Gem Color DOES NOT MATCH
    145.         if(gem.GemColorType != GemColorType)
    146.         {
    147.             return false;
    148.         }
    149.         //Gem Color DOES MATCH
    150.         else if(gem.GemColorType == GemColorType)
    151.         {
    152.             return true;
    153.         }
    154.             return false;
    155.     }
    156.        
    157. public static List<Gem> CheckForGemsThatMatch(Gem gem, AXIS axis, int minMatchLength)
    158.         {
    159.             List<Gem> result = new List<Gem>();
    160.             // add the given gem
    161.             result.Add(gem);
    162.             // find longest common match before our given gem
    163.             Gem check = gem;
    164.             while ((check = check.Previous(axis)) != null)
    165.             {
    166.                 if (gem.Matches(check))
    167.                 {
    168.                     // Prepend the matched gem
    169.                     result.Insert(0, check);
    170.                 }
    171.                 else
    172.                 {
    173.                     break;
    174.                 }
    175.             }
    176.             // find longest common match after out given gem
    177.             check = gem;
    178.             while ((check = check.Next(axis)) != null)
    179.             {
    180.                 if (gem.Matches(check))
    181.                 {
    182.                     // Append the matched gem
    183.                     result.Add(check);
    184.                 }
    185.                 else
    186.                 {
    187.                     break;
    188.                 }
    189.             }
    190.             return result.Count >= minMatchLength ? result : null;
    191.         }      
    192.        
    193.        
    194.        
    195.        
    196. public override string ToString ()
    197.         {
    198.              return GemColorType.ToString();
    199.         }
    200.        
    201. }
    202. }
    203.  
    204.  
    205.  
    GameBoard.cs
    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3. using System.Collections.Generic;
    4. //USAGE: GameBoard "yourBoardNameHere" = new GameBoard();
    5.  
    6. namespace GemTitan
    7. {
    8. public class GameBoard
    9. {
    10.     //Player Grid Dimensions Width,Height      
    11.     public const int BoardWidth = 6;
    12.     // 2 gems can spawn "above" the board visability when placing a new 3 gem stack on the grid 15-2 = VISIABLE
    13.     public    const int BoardHeight = 16;
    14.     //2D MultiDimensional Array of Type "Gem" named "playBoard"
    15.     private Gem[,] playBoard;
    16.  
    17.         public int Width
    18.         {
    19.             get
    20.             {
    21.                 return playBoard.GetLength(0);
    22.             }
    23.         }
    24.         public int Height
    25.         {
    26.             get
    27.             {
    28.                 return playBoard.GetLength(1);
    29.             }
    30.         }
    31.    
    32. public GameBoard()
    33.     {
    34.         InitGameBoard();
    35.     }
    36.        
    37. public void InitGameBoard()
    38.     {
    39.         playBoard = new Gem[BoardWidth, BoardHeight];
    40.         FillNewGridArrayOfTypeGemWithZeros();
    41.     }
    42.                
    43. private void FillNewGridArrayOfTypeGemWithZeros()
    44.     {
    45.         for(int x = 0; x < Width; x++)
    46.         {
    47.             for(int y = 0; y < Height; y++)
    48.             {
    49.                 playBoard[x,y] = new Gem(Gem.GEMTYPE.EMPTY, Gem.GEMMOD.NORMAL, x, y);
    50.             }
    51.         }
    52.     }
    53.    
    54. public Gem this[int x, int y]
    55.     {
    56.         get
    57.         {
    58.             return playBoard[x,y];
    59.         }
    60.         set
    61.         {
    62.             playBoard[x,y] = value;
    63.         }
    64.     }
    65.    
    66. public override string ToString ()
    67.  
    68.         {
    69.              string result = "";
    70.            
    71.             for(int x = 0; x < Width; x++)
    72.             {
    73.                 result += "[";
    74.                 for(int y = 0; y < Height; y++)
    75.                     {
    76.                         result += string.Format("{0} ",playBoard[x,y].ToString());
    77.                         if(y < Height - 1)
    78.                         {
    79.                             result += ", ";
    80.                         }
    81.                     }
    82.                 result += "]\n";
    83.             }
    84.             return result;
    85.         }
    86.        
    87. #region HELPERMETHODS
    88. //Duh...
    89. public static void CopyGridBoard(GameBoard source, GameBoard destination)
    90.     {
    91.         for (int y = 0; y <  BoardHeight; y++)
    92.         {
    93.             for (int x = 0; x < BoardWidth; x++)
    94.             {
    95.                 destination[x, y] = source[x, y];
    96.             }
    97.         }
    98.     }
    99.        
    100. #endregion
    101.  
    102. }
    103. }


    I wrote this with the hope that I am headed in the correct direction with what you guys were tying to explain as far as the match making portion of Gem class?

    I use GameObject Prefabs for the Visual representation of the gems... I select the correct prefab based on the color gem that is being requested to be created with the above code. I know this is one way to do it, but was wondering if this would work fine for THIS game because there are not going to be hundreds of prefabs for Gems, there are only 6 different colors per the rules or is there a better way perhaps? Before I was using a static variable that would hold the correct string name of the Prefab to use for whatever gem was being requested.
    Example:
    Code (CSharp):
    1. // Generate a GameObject DEFINED gem (RED,GREEN,etc.)  
    2. public static GameObject GenerateVisualGem(Vector3 pos, Gem.GEMTYPE gemColorType, Gem.GEMMOD gemMod)
    3.         {
    4.            
    5.  
    6.             //Return Prefab name that matches gemColorType in a static string named gemMats
    7.             Gem.GetGemPrefabName(gemColorType);
    8.             // Assign the Correct Gem Prefab from the enum Prefab list that GetGemPrefabName returned GemPrefabName
    9.             GameObject gemPrefab = Resources.Load("Prefabs/"+Gem.gemMat) as GameObject;
    10.             //"Plot" the gem gameobject to the screen
    11.             GemDroppingBox.obj = (GameObject) Instantiate(gemPrefab,pos,Quaternion.identity);
    12.             //Assign this Gem's TAG in the inspector to "Gem"
    13.             GemDroppingBox.obj.tag = "Gem";
    14.             //We dont want gem to show up on screen yet.
    15.             GemDroppingBox.obj.renderer.enabled = false;
    16.             //Return our newly created gameobject of a GEM.
    17.             return GemDroppingBox.obj;
    18.         }  
    19.  

    Once I get the "community approval" from you guys on the two above classes, meaning that you all feel they will be adequate for what I/we are trying to do here and aside from tweaking, they are considered "complete-ish". I will move on to building the next class, and questions. ;)

    Thanks
    King
     
  37. KingTooTall

    KingTooTall

    Joined:
    Oct 17, 2014
    Posts:
    61
    Update:
    Hi all !
    I have decided to make the "Gem Dropping Box" that is made up of 3 GEMS vertically on top of each other, with the same Horz position, its OWN class. Since it will be an object that does the same thing (basically) over and over, I thought it would be better to make it a separate object.
    I do have a question about an "addition" to this class? Somewhere (wherever we put it) on the screen there will be a visual of 3 more gems that represent the "next" gem set that will be the players next Gem dropping Box. Since it is directly related and basically does the same thing as the dropping box (but it doesn't MOVE, shuffle), should I make the "next piece" a part of the Gem Droping Box class? or part of the board class or a separate object?
    Here is part of the new class:

    GemDroppingBox.cs

    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3. using System.Collections.Generic;
    4.  
    5. namespace GemTitan
    6. {
    7. public class GemDroppingBox
    8. {
    9. #region Variables
    10. //For ease of access? Each gem has its own place holder for the dropping box
    11. public Gem TopGem;
    12. public Gem MiddleGem;
    13. public Gem BottomGem;
    14. //visual GAMEOBJECTS for the Gems
    15. public GameObject TopGemVisual;
    16. public GameObject MiddleGemVisual;
    17. public GameObject BottomGemVisual;
    18. //List holds the visual GemDroppingBox set of 3 gems.
    19. private List<GameObject> droppingBoxVisual = new List<GameObject>();
    20. //Array of TYPE Gem for dropping gem box
    21. private Gem[] droppingBox;
    22.  
    23. #endregion      
    24.      
    25. public GemDroppingBox(Gem.GEMTYPE gemColorTop,       Gem.GEMMOD gemModTop,
    26.                        Gem.GEMTYPE gemColorMiddle, Gem.GEMMOD gemModMiddle,
    27.                       Gem.GEMTYPE gemColorBottom, Gem.GEMMOD gemModBottom)
    28.          
    29.     {  
    30.         InitDroppingGems(gemColorTop, gemModTop, gemColorMiddle, gemModMiddle, gemColorBottom, gemModBottom);
    31.     }
    32.      
    33.  
    34. public void InitDroppingGems(Gem.GEMTYPE gemColorTop,      Gem.GEMMOD gemModTop,
    35.                                Gem.GEMTYPE gemColorMiddle, Gem.GEMMOD gemModMiddle,
    36.                                Gem.GEMTYPE gemColorBottom, Gem.GEMMOD gemModBottom)
    37.     {
    38.         droppingBoxVisual.Clear();
    39.         droppingBox = new Gem[3];
    40.         TopGemVisual = null;
    41.         MiddleGemVisual = null;
    42.         BottomGemVisual = null;
    43.         //Yes they are all null - Cheap way of LIST init....
    44.         droppingBoxVisual.Add(BottomGemVisual);// [0]
    45.         droppingBoxVisual.Add(MiddleGemVisual);// [1]
    46.         droppingBoxVisual.Add(TopGemVisual);   // [2]
    47.         GenerateGemsForDroppingBox(gemColorTop,gemModTop,gemColorMiddle,gemModMiddle,gemColorBottom,gemModBottom);
    48.         DisplayGemDroppingBox(true);
    49.         DisplayNextGemSet(); //STUB METHOD for now
    50.     }
    51.  
    52.      
    53.  
    54. //Craft Gems: GameObjects and Type <Gem> Gems
    55. public void GenerateGemsForDroppingBox(Gem.GEMTYPE gemColorTop,    Gem.GEMMOD gemModTop,
    56.                                            Gem.GEMTYPE gemColorMiddle, Gem.GEMMOD gemModMiddle,
    57.                                        Gem.GEMTYPE gemColorBottom, Gem.GEMMOD gemModBottom)
    58.     {
    59.         //Middle of board
    60.         int xPos = GameBoard.BoardWidth/2;
    61.         //One tile off TOP of visual board
    62.         int yPos = GameBoard.BoardHeight-3;
    63.     //BOTTOM Gem
    64.         if(gemColorBottom == Gem.GEMTYPE.RANDOM)
    65.         {
    66.             gemColorBottom = (Gem.GEMTYPE)Random.Range(1,(int)Gem.GEMTYPE.RANDOM);
    67.         }
    68.         if(gemModBottom == Gem.GEMMOD.RANDOM)
    69.         {
    70.             gemModBottom = (Gem.GEMMOD)Random.Range(1,(int)Gem.GEMMOD.RANDOM);
    71.         }
    72.         droppingBox[0] = new Gem(gemColorBottom,gemModBottom,xPos,yPos);
    73.         BottomGem = droppingBox[0];          
    74.         droppingBoxVisual[0] = CoreGameManager.GenerateVisualGem(BottomGem);
    75.         BottomGemVisual = droppingBoxVisual[0];
    76.     //MIDDLE Gem
    77.         if(gemColorMiddle == Gem.GEMTYPE.RANDOM)
    78.         {
    79.             gemColorMiddle = (Gem.GEMTYPE)Random.Range(1,(int)Gem.GEMTYPE.RANDOM);
    80.         }
    81.         if(gemModMiddle == Gem.GEMMOD.RANDOM)
    82.         {
    83.             gemModMiddle = (Gem.GEMMOD)Random.Range(1,(int)Gem.GEMMOD.RANDOM);
    84.         }
    85.         droppingBox[1] = new Gem(gemColorMiddle,gemModMiddle,xPos,yPos+1);
    86.         MiddleGem = droppingBox[1];
    87.         droppingBoxVisual[1] = CoreGameManager.GenerateVisualGem(MiddleGem);
    88.         MiddleGemVisual = droppingBoxVisual[1];
    89.     //TOP Gem
    90.         if(gemColorTop == Gem.GEMTYPE.RANDOM)
    91.         {
    92.             gemColorTop = (Gem.GEMTYPE)Random.Range(1,(int)Gem.GEMTYPE.RANDOM);
    93.         }
    94.         if(gemModTop == Gem.GEMMOD.RANDOM)
    95.         {
    96.             gemModTop = (Gem.GEMMOD)Random.Range(1,(int)Gem.GEMMOD.RANDOM);
    97.         }
    98.         droppingBox[2] = new Gem(gemColorTop,gemModTop,xPos,yPos+2);
    99.         TopGem = droppingBox[2];
    100.         droppingBoxVisual[2] = CoreGameManager.GenerateVisualGem(TopGem);
    101.         TopGemVisual = droppingBoxVisual[2];
    102.     }
    103.  
    There is more code to this class also, but its just some simple methods to shuffle the gems in the dropping box, movement (down, left/right), and a few other misc helper methods. Please comment on ANY of the code above if you wish, not just answer my question. ;)

    Edit:
    Added sample code of how I use the class. :)

    Code (CSharp):
    1.             fallingGems = new GemDroppingBox( Gem.GEMTYPE.RANDOM,Gem.GEMMOD.NORMAL,
    2.                                                               Gem.GEMTYPE.RANDOM,Gem.GEMMOD.NORMAL,
    3.                                                               Gem.GEMTYPE.RANDOM,Gem.GEMMOD.NORMAL );
    Thanks
    King
     
  38. KingTooTall

    KingTooTall

    Joined:
    Oct 17, 2014
    Posts:
    61
    Ok, So I have updated a few classes and changed things around a bit to make the source code a bit more readable and easier to understand. I dare say I have (loosely) optimized some of the code also.
    I modified the version of what R.Lindsay was giving as an example, here is the Method for Match Making that WORKS!
    Code (csharp):
    1.  
    2.  
    3. public static void ContiguousNeighborGems(Gem gem, AXIS axis, int minMatchLength)
    4.  {
    5.    List<Gem> result = new List<Gem>();
    6.   result.Add(gem);
    7.   Gem check = gem;
    8.  //Find longest common match BEFORE our given gem
    9.   while ((check = check.Previous(axis)) != null)
    10.   {
    11.   if (gem.Matches(check))
    12.   {
    13.   result.Insert(0, check);
    14.   }
    15.   else
    16.   {
    17.   break;
    18.   }
    19.   }
    20.   //Find longest common match AFTER our given gem
    21.   check = gem;
    22.   while ((check = check.Next(axis)) != null)
    23.   {
    24.   if (gem.Matches(check))
    25.   {
    26.   result.Add(check);
    27.   }
    28.   else
    29.   {
    30.   break;
    31.   }
    32.   }
    33.  //Check if gems in the matched list are already in the final list, if so DONT add them again
    34.  if(result.Count >= minMatchLength)
    35.  {
    36.  for(int i = 0; i < result.Count;i++)
    37.  {
    38.  if(result[i].MarkedForDeletion != true)
    39.  {
    40.  FinalListOfGemsToDestroy.Add(result[i]);
    41.  result[i].MarkedForDeletion = true;
    42.  }
    43.  }
    44.  }
    45.   }
    46.  
    47. public static void BuildListOfMatchingGems()
    48.  {
    49.  int minMatchLength = 3;
    50.  FinalListOfGemsToDestroy.Clear();
    51.  for(int i = 0;i < BaseGemsToCheckForMatch.Count;i++)
    52.  {
    53.  ContiguousNeighborGems(BaseGemsToCheckForMatch[i],AXIS.HORIZONTAL,minMatchLength);
    54.  ContiguousNeighborGems(BaseGemsToCheckForMatch[i],AXIS.VERTICAL,minMatchLength);
    55.  ContiguousNeighborGems(BaseGemsToCheckForMatch[i],AXIS.DIAGONAL_POS,minMatchLength);
    56.  ContiguousNeighborGems(BaseGemsToCheckForMatch[i],AXIS.DIAGONAL_NEG,minMatchLength);
    57.  }
    58.  //DONE with BaseGem list, so clear for the next pass
    59.  BaseGemsToCheckForMatch.Clear ();
    60.  //If there is anything in the final list of GEMS, then Destroy Gem GO's,Resettle board..check for NEW matches,  REPEAT?!?!
    61.  if(FinalListOfGemsToDestroy != null)
    62.  {
    63.  //Remove all Gems that match off the screen
    64.  CoreGameManager.DestroyMatchingGems(FinalListOfGemsToDestroy);
    65.  //Removing Gems may have caused Gaps, ReSettle the board
    66.  GameBoard.SettleBlocks(CoreGameManager.gridBoard);
    67.  //Scan to make sure that there are no tiles ABOVE the game over height.
    68.  for(int x=0;x< GameBoard.BoardWidth;x++)
    69.  {
    70.  if(CoreGameManager.gridBoard[x,CoreGameManager.gameOverHeight].GemColorType != GEMTYPE.EMPTY)
    71.  {
    72.  CoreGameManager.isGameOver = true;
    73.  }
    74.  }
    75.  }
    76.  if(BaseGemsToCheckForMatch.Count != 0)
    77.  {
    78.  //Resettle may have caused NEW matches check for matching gems again..
    79.  BuildListOfMatchingGems();
    80.  }
    81.  }
    82.  
    I have also decided to make the Gem Dropping Box and the NEXT piece's their own separate classes (as I did NOT get anyone's response on IF I should or should not?). The reason I made them separate (even though some of the code is similar) was to make it easier to change the next pieces "on the fly". (e.g a opponent gets a "powerup" that changes the next piece BEFORE the other play drops the CURRENT pieces to bottom of grid) The dropping box "pulls" its gems from the NEXT piece. The two Classes are attached for your laughter, errr review should anyone like to comment along with the GEM class and the GameBoard class which together have logic for resettling the board after matches are made. I'm sure you guys are probably sick of hearing about this little project, but I'd thought I'd share what I have learned thus far from everyone's help and comments. More to come.

    Thanks
    King
     

    Attached Files:

  39. R-Lindsay

    R-Lindsay

    Joined:
    Aug 9, 2014
    Posts:
    287
    Good to see you have a successful matchmaking method!

    I don't have any more time to help you or look over you code since my own project is getting busy. But I looks like you are making progress. Remember to look back over the advice people have given you and you'll continue to grow.

    good luck
     
  40. KingTooTall

    KingTooTall

    Joined:
    Oct 17, 2014
    Posts:
    61
    R.Lindsay, You and the rest of the folks, have been tremendous help. The hard part was the match making and I seem to have the whooped after your advice. Along the way I have learned a heap too. I understand your busy, and I'm not asking for you to interrupt what ya have going on, but if you wouldn't mind, please for the very least, just keep following this project when you have a moment to read the posts? ;)
    For sure! I have done that several times now already, Lol!
    Thanks Again guys, I will continue to post progress as I make it, and share this experience with the community.

    King
     
    angrypenguin likes this.