How to code a match 3 game and backtracking algorithm basics.

Discussion in 'Tutorials' started by tagwolf, Jul 28, 2019.

  1. tagwolf

    tagwolf Member

    Joined:
    Aug 6, 2016
    Posts:
    59
    GM Version: GameMaker Studio 2.2.3
    Target Platform: Windows / All
    Download: N/A (TBD)
    Links: https://www.raywenderlich.com/55-how-to-make-a-game-like-candy-crush-with-spritekit-and-swift-part-1 (I used this initially when I was struggling for base logic, but ultimately this is a backtracking algorithm at the core.)

    EXAMPLE PROJECT: https://host-a.net/f/226891-blox3-working-buyyz

    Summary:
    How to code a match 3 game and backtracking algorithm basics.

    Features:
    Heavily script based (I used scripts for all the major game logic functions so hopefully you'll be able to rip them out and adapt them to your use case. I am trying to make more and more of my games and tutorials utilize modular scripts. It's best practice in other languages to use functions when possible. It makes it easy to share out and reuse code in other projects.
    • Hint system (shows a random valid move from the list of valid moves)
    • Well commented backtracking algorithms
    • Scripts included:
      • scr_create_playfield
      • scr_find_swaps
      • scr_find_chain_at
      • scr_test_swap
      • scr_do_swap
      • scr_clear_matches
      • scr_find_matches
      • scr_do_fall
      • scr_do_spawn
      • scr_find_hint

    Tutorial:
    Oh backtracking algorithms. How you gave me multiple migraines when I was first starting out. I know this can be a complex topic. Believe it or not, a match 3 game has a LOT more logic in it then you might initially think.

    If you consider everything the computer has to do. Just to setup the gameboard it needs to do a check on every cell to make sure there are no existing matches.

    When you make a swap, you need to check every direction of both tiles you swapped, you have to clear them and move piece above down into place and then when they are cleared, evaluate everything that has changed to check if there are more matches.

    You have to check if there are zero valid moves to see when it's game over. The list goes on and on.

    ** NOTE **

    I'm trying to find the best place to host the project for this forum to ensure this will be accessible. Until I get that information I'm going to at least put each script up here on this forum post. But it'll be much better when you can see the assets and how everything works. Still I hope that for someone working on something the scripts alone will provide some assistance to you.

    scr_create_playfield

    Code:
    /// Creates a clean playfield
    
    for (r = 0; r < playfield_rows; r++) {
    
       for (c = 0; c < playfield_columns; c++) {
    
           cell = instance_create_layer(
           (c * (cell_size + cell_padding)),
           (r * (cell_size + cell_padding)),
           "Instances",
           obj_cell);
         
           // TODO:
           //Implement check left+1,left+2,up+1,up+2
         
           var cell_left1 = instance_position(((cell_size + cell_padding) * (c - 1)), ((cell_size + cell_padding) * r), obj_cell);
           var cell_left2 = instance_position(((cell_size + cell_padding) * (c - 2)), ((cell_size + cell_padding) * r), obj_cell);
           var cell_up1 =   instance_position(((cell_size + cell_padding) * c), ((cell_size + cell_padding) * (r - 1)), obj_cell);
           var cell_up2 =   instance_position(((cell_size + cell_padding) * c), ((cell_size + cell_padding) * (r - 2)), obj_cell);
         
           // If empty
           if (cell_left1 == noone) {var cell_left1_value = 0} else {var cell_left1_value = cell_left1.cell_value};
           if (cell_left2 == noone) {var cell_left2_value = 0} else {var cell_left2_value = cell_left2.cell_value};
           if (cell_up1 == noone) {var cell_up1_value = 0} else {var cell_up1_value = cell_up1.cell_value};
           if (cell_up2 == noone) {var cell_up2_value = 0} else {var cell_up2_value = cell_up2.cell_value};
         
           // Check left
           if (cell.cell_value == cell_left1_value && cell_left1_value == cell_left2_value) {
               var cell_bad_color = cell.cell_value;
             
               while (cell_bad_color == cell.cell_value) {
                   cell.cell_value = irandom_range(1,6);
                   cell.image_index = cell.cell_value;
               }
             
           }
         
           // Check up
           if (cell.cell_value == cell_up1_value && cell_up1_value == cell_up2_value) {
               var cell_bad_color = cell.cell_value;
             
               while (cell_bad_color == cell.cell_value) {
                   cell.cell_value = irandom_range(1,6);
                   cell.image_index = cell.cell_value;
               }
             
           }
         
           // Set color again (if not set from being a match)
           cell.image_index = cell.cell_value;
     
       }
     
    }
    
    scr_find_swaps

    Code:
    ///scr_find_swaps()
    // Returns an array of valid swaps
    
    //var cell_origin = noone;
    //var cell_target = noone;
    var swaps_found = 0;
    var swaps_found_total = 0;
    
    // Loop through grid (same as create playfield)
    for (r = 0; r < playfield_rows; r++) {
    
       for (c = 0; c < playfield_columns; c++) {
     
           // for each cell, swap cell right, check, swap cell down, check.
           // if I ever do shaped grids, this will need to detect empties.
           var cell = instance_position(c * (cell_size + cell_padding), (r * (cell_size + cell_padding)), obj_cell);
           var cell_right1 = instance_position(((cell_size + cell_padding) * (c + 1)), ((cell_size + cell_padding) * r), obj_cell);
           var cell_down1 = instance_position(((cell_size + cell_padding) * c), ((cell_size + cell_padding) * (r + 1)), obj_cell);
         
           /*
           // Debugging positioning of cells
           show_debug_message("c * size + padding = " + string(c * (cell_size + cell_padding)));
           show_debug_message("r * size + padding = " + string(r * (cell_size + cell_padding)));
           show_debug_message("size + padding * c + 1 = " + string(((cell_size + cell_padding) * (c + 1))));
           */
     
           // I need to detect empties anyways.
           // swap cells if not empty
         
           // make sure origin isn't empty
           // I think the empties might be causing the deleting column.
           // How to handle this...
           // Wait, they shouldn't be..
           // cause they wont swap in the first place if either is empty..
           if (cell != noone) {
             
               // check right
               if (cell_right1 != noone) {
             
                   //show_debug_message("RIGHT CHECK");
                 
                   // cell to right exists
                   // save origin and targets so we can swap back and store if valid
                   var cell_origin_x = cell.x
                   var cell_target_x = cell_right1.x
                 
                   /*
                   // Debugging for swap tests
                   show_debug_message("cell_origin: " + string(cell_origin));
                   show_debug_message("cell_target: " + string(cell_target));
                   show_debug_message("cell_right1: " + string(cell_right1));
                   show_debug_message("cell_origin_x: " + string(cell_origin_x));
                   show_debug_message("cell_target_x: " + string(cell_target_x));
                   show_debug_message("cell_right1.x: " + string(cell_right1.x));
                   */
                         
                   // swap right
                   cell.x = cell_target_x;
                   cell_right1.x = cell_origin_x;
                 
                   // test from origin and from target
                   // and the one to the right
                   if (scr_find_chain_at(c, r) || scr_find_chain_at(c + 1, r)) {
                       swaps_found[swaps_found_total, 0] = cell;
                       swaps_found[swaps_found_total, 1] = cell_right1;
                       swaps_found_total += 1;
                   }
    
                   // when I was using x,y. Blah. No good solutions except storing
                   // it all in a grid. Next time.             
                   //scr_find_chain_at(cell_origin.x,cell_origin.y);
                   //scr_find_chain_at(cell_target.x,cell_target.y);
         
                   // look down until a non-match OR an empty is found
                   // check if the match length is 3 or more
                   // if it is, store the swap as a valid move.
                 
                   // swap back
                   cell.x = cell_origin_x;
                   cell_right1.x = cell_target_x;
    
     
               } else {
     
                   // cell to right doesn't exist
                   // do we need to even do anything?
                   //show_debug_message("no cell to right, skipping swap test.");
     
               }
             
             
               // check down
               if (cell_down1 != noone) {
             
                   //show_debug_message("DOWN CHECK");
                 
                   // cell below exists
                   // save origin and targets so we can swap back and store if valid
                   var cell_origin_y = cell.y
                   var cell_target_y = cell_down1.y
                 
                   /*
                   // Debugging for swap tests
                   show_debug_message("cell_origin: " + string(cell_origin));
                   show_debug_message("cell_target: " + string(cell_target));
                   show_debug_message("cell_down1: " + string(cell_down1));
                   show_debug_message("cell_origin_x: " + string(cell_origin_x));
                   show_debug_message("cell_target_x: " + string(cell_target_x));
                   show_debug_message("cell_down1.x: " + string(cell_down1.x));
                   */
                 
                   // swap down
                   cell.y = cell_target_y;
                   cell_down1.y = cell_origin_y;
                 
                   // test from origin and from target
                   // and the one below
                   if (scr_find_chain_at(c, r) || scr_find_chain_at(c, r + 1)) {
                       swaps_found[swaps_found_total, 0] = cell;
                       swaps_found[swaps_found_total, 1] = cell_down1;
                       swaps_found_total += 1;
                   }
                 
                   // when I was using x,y. Blah. No good solutions except storing
                   // it all in a grid. Next time.             
                   //scr_find_chain_at(cell_origin.x,cell_origin.y);
                   //scr_find_chain_at(cell_target.x,cell_target.y);
         
                   // look down until a non-match OR an empty is found
                   // check if the match length is 3 or more
                   // if it is, store the swap as a valid move.
                 
                   // swap back
                   cell.y = cell_origin_y;
                   cell_down1.y = cell_target_y;
    
     
               } else {
     
                   // cell below doesn't exist
                   // do we need to even do anything?
                   //show_debug_message("no cell below, skipping swap test.");
     
               }
     
           }
     
       }
     
    }
    
    // This should probably return an array of swaps.
    show_debug_message("Total swaps found: " + string(swaps_found_total));
    
    // Iterate all swaps for debug purposes
    for (i=0; i < swaps_found_total; i++) {
       show_debug_message("Origin swap: " + "(" + string(i) + ")" + string(swaps_found[i, 0]));
       show_debug_message("Target swap: " + "(" + string(i) + ")" + string(swaps_found[i, 1]));
    }
    
    if (swaps_found_total == 0) {
       // no swaps found
       // probably a game ender!
       show_debug_message("NO SWAPS AVAILABLE!");
       obj_score.game_over = true;
       return false;
    } else {
       show_debug_message("SWAPS AVAILABLE! RETURNING ARRAY");
       return swaps_found;
    }
    
    scr_find_chain_at

    Code:
    ///scr_find_chain_at(column,row)
    // Given a cell object id, it will search up, down, left, right for a chain
    // of match_minimum_length at the given coordinates.
    
    // Returns true if there is a chain of minimum length or more.
    
    var match_minimum_length = 3;
    var cell_test_origin_column = argument0;
    var cell_test_origin_row = argument1;
    var cell_test_next = 0;
    
    var match_column_length = 1;
    var match_row_length = 1;
    
    // find origin cell
    var cell = instance_position(c * (cell_size + cell_padding), (r * (cell_size + cell_padding)), obj_cell);
    var cell_test_origin = instance_position(cell_test_origin_column * (cell_size + cell_padding), cell_test_origin_row * (cell_size + cell_padding), obj_cell);
    
    //show_debug_message("cell origin to test: " + string(cell_test_origin));
    //show_debug_message("cell origin col,row: " + string(cell_test_origin_column) + "," + string(cell_test_origin_row));
    
    // look left
    var cell_test_column = cell_test_origin_column - 1;
    var cell_test_row = cell_test_origin_row;
    var cell_test_next = instance_position(((cell_size + cell_padding) * cell_test_column), ((cell_size + cell_padding) * cell_test_row), obj_cell);
    while (cell_test_column >= 0 && cell_test_next != noone) {
    
       //show_debug_message("left loop");
    
       cell_test_next = instance_position(((cell_size + cell_padding) * cell_test_column), ((cell_size + cell_padding) * cell_test_row), obj_cell);
    
       if (cell_test_next != noone) {
     
           if (cell_test_origin.cell_value == cell_test_next.cell_value) {
               //show_debug_message("left loop + match");
               match_column_length += 1;
               cell_test_column -= 1;
           } else {
               cell_test_next = 0;
               break;
           }
     
       }
    
    }
    
    // look right
    var cell_test_column = cell_test_origin_column + 1;
    var cell_test_row = cell_test_origin_row;
    var cell_test_next = instance_position(((cell_size + cell_padding) * cell_test_column), ((cell_size + cell_padding) * cell_test_row), obj_cell);
    while (cell_test_column <= playfield_columns && cell_test_next != noone) {
    
       //show_debug_message("right loop");
    
       cell_test_next = instance_position(((cell_size + cell_padding) * cell_test_column), ((cell_size + cell_padding) * cell_test_row), obj_cell);
    
       if (cell_test_next != noone) {
     
           if (cell_test_origin.cell_value == cell_test_next.cell_value) {
               //show_debug_message("right loop + match");
               match_column_length += 1;
               cell_test_column += 1;
           } else {
               cell_test_next = 0;
               break;
           }
     
       }
    
    }
    
    // look up
    var cell_test_column = cell_test_origin_column;
    var cell_test_row = cell_test_origin_row - 1;
    var cell_test_next = instance_position(((cell_size + cell_padding) * cell_test_column), ((cell_size + cell_padding) * cell_test_row), obj_cell);
    while (cell_test_row >= 0 && cell_test_next != noone) {
    
       //show_debug_message("up loop");
    
       cell_test_next = instance_position(((cell_size + cell_padding) * cell_test_column), ((cell_size + cell_padding) * cell_test_row), obj_cell);
    
       if (cell_test_next != noone) {
     
           if (cell_test_origin.cell_value == cell_test_next.cell_value) {
               //show_debug_message("up loop + match");
               match_row_length += 1;
               cell_test_row -= 1;
           } else {
               cell_test_next = 0;
               break;
           }
             
       }
    
    }
    
    // look down
    var cell_test_column = cell_test_origin_column;
    var cell_test_row = cell_test_origin_row + 1;
    var cell_test_next = instance_position(((cell_size + cell_padding) * cell_test_column), ((cell_size + cell_padding) * cell_test_row), obj_cell);
    while (cell_test_row <= playfield_rows && cell_test_next != noone) {
    
       //show_debug_message("down loop");
    
       cell_test_next = instance_position(((cell_size + cell_padding) * cell_test_column), ((cell_size + cell_padding) * cell_test_row), obj_cell);
     
       if (cell_test_next != noone) {
     
           if (cell_test_origin.cell_value == cell_test_next.cell_value) {
               //show_debug_message("down loop + match");
               match_row_length += 1;
               cell_test_row += 1;
           } else {
               cell_test_next = 0;
               break;
           }
     
       }
    
    }
    
    //show_debug_message("cell origin tested: " + string(cell_test_origin_column) + "," + string(cell_test_origin_row));
    //show_debug_message("match_column_length: " + string(match_column_length));
    //show_debug_message("match_row_length: " + string(match_row_length));
    
    if (match_column_length >= match_minimum_length || match_row_length >= match_minimum_length) {
    
       //show_debug_message("match_minimum_length met!");
       return true;
    
    } else {
    
       //show_debug_message("match_minimum_length not met.");
       return false;
     
    }
    
    
    scr_test_swap
    Code:
    ///scr_test_swap(swap1, swap2)
    
    // Returns true if valid swap found
    
    var valid_swaps = scr_find_swaps();
    var swap1 = argument0;
    var swap2 = argument1;
    
    if (valid_swaps == false) {
       // This should likely be handled in the find swaps script. Not here.
       show_debug_message("No valid swaps found. Game over?");
     
    } else {
    
       var valid_swaps_total = array_height_2d(valid_swaps);
     
       for (i = 0; i < valid_swaps_total; i++) {
     
           if ((swap1 == valid_swaps[i,0] && swap2 == valid_swaps[i,1]) ||
               (swap2 == valid_swaps[i,0] && swap1 == valid_swaps[i,1])) {
         
               return true;
               exit;
             
           }
     
       }
     
       return false;
    
    }
    
    scr_do_swap

    Code:
    ///scr_do_swap(cell1,cell2)
    
    // Performs an animated swap of 2 gems
    
    // Perhaps also handles the scripting to make the board unstable,
    // clear matches (maybe call spawn here), spawn cells, etc.
    
    var cell1 = argument0;
    var cell2 = argument1;
    
    var swap_speed = 2;
    
    var cell1_origin_x = cell1.x;
    var cell1_origin_y = cell1.y;
    var cell2_origin_x = cell2.x;
    var cell2_origin_y = cell2.y;
    
    // Do the swap!
    cell1.x = cell2_origin_x;
    cell1.y = cell2_origin_y;
    cell2.x = cell1_origin_x;
    cell2.y = cell1_origin_y;
    
    playfield_stable = false;
    
    
    // TODO ANIMATE. Easy. Lets just make sure this works first.
    
    // Not so easy. Needs to be in a step event.
    
    /*
    with (cell1) {
       if (point_distance(x, y, cell2_origin_x, cell2_origin_y) > 0) {
           move_towards_point(cell2_origin_x, cell2_origin_y, swap_speed);
       } else {
           speed = 0;
       }
    }
    
    with (cell2) {
       if (point_distance(x, y, cell1_origin_x, cell1_origin_y) > 0) {
           move_towards_point(cell1_origin_x, cell1_origin_y, swap_speed);
       } else {
           speed = 0;
       }
    }
    
    // Once they are stopped, suspend animation step
    if (cell1.speed == 0 && cell2.speed == 0) {
       any_cell_swapping = false;
    }
    */
    
    scr_clear_matches

    Code:
    ///scr_clear_matches()
    // Clears all matches found
    
    var valid_matches = scr_find_matches();
    
    // TODO: Add if statement in case of no valid matches / false?
    
    if (valid_matches == false) {
    
       return false;
    
    } else {
    
       for (i = array_height_2d(valid_matches) - 1; i > -1; i--) {
    
           for (j = array_length_2d(valid_matches, i) - 1; j > -1; j--) {
           show_debug_message("Destroying match: " + string(valid_matches[i,j]));
           // TODO: Particle effect or image warp on destroy
           instance_destroy(valid_matches[i, j]);
           score += 1;
           playfield_stable = false;
    
           }
     
       }
    
       return true;
    
    }
    
    scr_find_matches

    Code:
    ///scr_find_matches()
    
    // Iterates playfield and finds all matches over minimum (3 typically.)
    
    // For each cell, it will search columns and then rows
    // of match_minimum_length.
    
    var match_minimum_length = 3;
    
    var cell_test_next = 0;
    
    var match_length = 0;
    var total_matches = 0;
    // For temp holding matches to count
    var test_matches = 0;
    // Will be a 1d array of objects to clear.
    // Not worried about scoring multipliers etc.
    var valid_matches = 0;
    
    show_debug_message("running scr_find_matches()");
    
    // check columns
    for (r = 0; r <= playfield_rows; r++) {
    
       for (c = 0; c <= playfield_columns; c++) {
    
           var cell_test_origin = instance_position(c * (cell_size + cell_padding), r * (cell_size + cell_padding), obj_cell);
           var cell_test_next = instance_position((c + 1) * (cell_size + cell_padding), r * (cell_size + cell_padding), obj_cell);
           //show_debug_message("col check origin id: " + string(cell_test_origin));
           //show_debug_message("col check next id: " + string(cell_test_next));
         
           // check if origin is noone
           if (cell_test_origin != noone) {
               var cell_test_origin_value = cell_test_origin.cell_value;
           } else {
               var cell_test_origin_value = -1;
           }
         
           // check if next is noone
           if (cell_test_next != noone) {
               var cell_test_next_value = cell_test_next.cell_value;
           } else {
               var cell_test_next_value = -1;
           }
         
           // store origin cell
           test_matches[match_length] = cell_test_origin;
                 
           if (cell_test_origin_value == cell_test_next_value && cell_test_origin_value != -1 && cell_test_next_value != -1) {
               // match exists
               // increment current chain length
               match_length += 1;
               // store next cell
               test_matches[match_length] = cell_test_next;
             
             
           } else {
         
               // next cell doesn't match or doesn't exist
               // check if at or over minimum match length
               if (match_length >= match_minimum_length - 1) {
                   // enough matches
                   // save to good matches
                   for (i=0; i <= match_length; i++) {
                 
                       valid_matches[total_matches,i] = test_matches[i];
                     
                       //show_debug_message("current match_length: " + string(match_length));
                       //show_debug_message("i: " + string(i));
                       //show_debug_message("valid matches: " + string(valid_matches[total_matches,i]))
                 
                   }
                 
                   // Increment total_matches
                   total_matches += 1;
                   //show_debug_message("total matches: " + string(total_matches));
                 
               }
             
               // debug match length
               //show_debug_message("post-test matches: " + string(match_length));
             
               // reset match_length for next test
               match_length = 0;
             
           }
    
       }
     
    }
    
    
    // check rows
    for (c = 0; c <= playfield_columns; c++) {
    
       for (r = 0; r <= playfield_rows; r++) {
    
           var cell_test_origin = instance_position(c * (cell_size + cell_padding), r * (cell_size + cell_padding), obj_cell);
           var cell_test_next = instance_position(c * (cell_size + cell_padding), (r + 1) * (cell_size + cell_padding), obj_cell);
           //show_debug_message("row check origin id: " + string(cell_test_origin));
           //show_debug_message("row check next id: " + string(cell_test_next));
         
           // check if origin is noone
           if (cell_test_origin != noone) {
               var cell_test_origin_value = cell_test_origin.cell_value;
           } else {
               var cell_test_origin_value = -1;
           }
         
           // check if next is noone
           if (cell_test_next != noone) {
               var cell_test_next_value = cell_test_next.cell_value;
           } else {
               var cell_test_next_value = -1;
           }
         
           // store origin cell
           test_matches[match_length] = cell_test_origin;
                 
           if (cell_test_origin_value == cell_test_next_value && cell_test_origin_value != -1 && cell_test_next_value != -1) {
               // match exists
               // increment current chain length
               match_length += 1;
               // store next cell
               test_matches[match_length] = cell_test_next;
    
             
           } else {
         
               // next cell doesn't match or doesn't exist
               // check if at or over minimum match length
               if (match_length >= match_minimum_length - 1) {
                   // enough matches
                   // save to good matches
                   for (i=0; i <= match_length; i++) {
                 
                       valid_matches[total_matches,i] = test_matches[i];
                     
                       //show_debug_message("current match_length: " + string(match_length));
                       //show_debug_message("i: " + string(i));
                       //show_debug_message("valid matches: " + string(valid_matches[total_matches,i]))
                 
                   }
                 
                   // Increment total_matches
                   total_matches += 1;
                   //show_debug_message("total matches: " + string(total_matches));
                 
               }
             
               // debug match length
               //show_debug_message("post-test matches: " + string(match_length));
             
               // reset match_length for next test
               match_length = 0;
             
           }
    
       }
     
    }
    
    show_debug_message("Total matches: " + string(total_matches));
    show_debug_message("Done running scr_find_matches.")
    
    for (i=0; i < total_matches; i++) {
    
       match_list_length = array_length_2d(valid_matches, i);
     
       show_debug_message("Match list length: " + string(match_list_length));
    
       for (j=0; j < match_list_length; j++) {
           show_debug_message("total matches list [" + string(i) + "]: " + string(valid_matches[i,j]));
       }
     
    }
    
    if (total_matches > 0) {
       return valid_matches;
    } else {
       return false;
    }
    
    scr_do_fall

    Code:
    ///scr_do_fall()
    // Makes cells with nothing below fall (swap) downward
    // until there's something below or they hit the last row.
    
    /*
    Steps:
    
    Iterate cells.
    
    Check under cell.
    If under is EMPTY AND Cell is above playfield_rows lowest row y
    Move cell down one.
    
    */     
    
    show_debug_message("scr_do_fall()");
    
    var cell_fell = false;
    
    for (r = 0; r < playfield_rows; r++) {
    
       for (c = 0; c < playfield_columns; c++) {
    
           // Current cell
           var cell = instance_position(c * (cell_size + cell_padding), (r * (cell_size + cell_padding)), obj_cell);
           var cell_down1 = instance_position(((cell_size + cell_padding) * c), ((cell_size + cell_padding) * (r + 1)), obj_cell);
         
           if (cell != noone) {
         
               if (cell_down1 == noone && r < playfield_rows - 1) {
             
                   cell.y += (cell_size + cell_padding);
                   //show_debug_message("falling");
                   var cell_fell = true;
                   return true;
                   //exit;
             
               }
         
           }
         
       }
    
    }
    
    //show_debug_message("not falling");
    //return false;
    
    
    // repeat until all cells have fallen to lowest point
    if (cell_fell) {
       cell_fell = false;
       scr_do_fall();
    } else {
       // Restabilize playfield
       //playfield_stable = true;
       //show_debug_message("not falling");
       return false;
    }
    
    
    scr_do_spawn

    Code:
    ///scr_do_spawn()
    
    // Things to do:
    /*
    Iterate over each cell for a column
    Check if empty
    If it is, add to spawn length + 1
    If it isn't, spawn cells x spawn length in that column (worry about falling later)
     Make sure we don't spawn more than 2 matches in a row. (same as when creating)
    
    
    */
    
    for (r = 0; r < playfield_rows; r++) {
    
       for (c = 0; c < playfield_columns; c++) {
    
           cell_check = instance_position((c * (cell_size + cell_padding)),(r * (cell_size + cell_padding)), obj_cell);
         
           if (cell_check == noone) {
               cell = instance_create_layer(
               (c * (cell_size + cell_padding)),
               (r * (cell_size + cell_padding)),
               "Instances",
               obj_cell);
         
               var cell_left1 = instance_position(((cell_size + cell_padding) * (c - 1)), ((cell_size + cell_padding) * r), obj_cell);
               var cell_left2 = instance_position(((cell_size + cell_padding) * (c - 2)), ((cell_size + cell_padding) * r), obj_cell);
               var cell_up1 =   instance_position(((cell_size + cell_padding) * c), ((cell_size + cell_padding) * (r - 1)), obj_cell);
               var cell_up2 =   instance_position(((cell_size + cell_padding) * c), ((cell_size + cell_padding) * (r - 2)), obj_cell);
         
               // If empty
               if (cell_left1 == noone) {var cell_left1_value = 0} else {var cell_left1_value = cell_left1.cell_value};
               if (cell_left2 == noone) {var cell_left2_value = 0} else {var cell_left2_value = cell_left2.cell_value};
               if (cell_up1 == noone) {var cell_up1_value = 0} else {var cell_up1_value = cell_up1.cell_value};
               if (cell_up2 == noone) {var cell_up2_value = 0} else {var cell_up2_value = cell_up2.cell_value};
         
               // Check left
               if (cell.cell_value == cell_left1_value && cell_left1_value == cell_left2_value) {
                   var cell_bad_color = cell.cell_value;
             
                   while (cell_bad_color == cell.cell_value) {
                       cell.cell_value = irandom_range(1,6);
                       cell.image_index = cell.cell_value;
                   }
             
               }
         
               // Check up
               if (cell.cell_value == cell_up1_value && cell_up1_value == cell_up2_value) {
                   var cell_bad_color = cell.cell_value;
             
                   while (cell_bad_color == cell.cell_value) {
                       cell.cell_value = irandom_range(1,6);
                       cell.image_index = cell.cell_value;
                   }
             
               }
         
               // Set color again (if not set from being a match)
               cell.image_index = cell.cell_value;
     
           }
     
       }
     
    }
    
    
    scr_find_hint
    Code:
    ///scr_find_hint()
    // Sets random valid to blink.
    // Also will call find swaps which could end game if no moves.
    var valid_swaps = scr_find_swaps();
    
    if (valid_swaps != false) {
    
       var valid_swaps_total = array_height_2d(valid_swaps) - 1;
    
       var random_hint_number = irandom(valid_swaps_total);
       var random_hint = valid_swaps[random_hint_number, 1];
    
       // Blink cell
       random_hint.cell_blink = true;
    
    } else {
    
       show_debug_message("NO HINTS AVAILABLE!");
    
    }
    
     
    Last edited: Aug 5, 2019
    Catch likes this.
  2. Catch

    Catch Member

    Joined:
    Feb 11, 2019
    Posts:
    2
    Hey Tagwolf
    I've been using scr_clear_matches and scr_find_matches for a practice project im working on. The scripts work great except i'm getting a massive drop in frame rate when running them. Have you experience that or is it just my ****ty computer? Could there be a way to optimize them perhaps? Ive been trying to check for tile data rather then instances but seems to get the same issue.
    cheers
     
  3. tagwolf

    tagwolf Member

    Joined:
    Aug 6, 2016
    Posts:
    59
    Hmm, I did not get any framedrop on my laptop of desktop pc. I'd need to do a debug and use the profiler to see what is costing so much. How often are you running scr_clear_matches and scr_find_matches? They definitely should not be run every frame as they need to do a pretty cpu intensive set of calculations. I'd only run them at an alarm interval or specifically when the player has made a move and then again after each valid move is found and cleared until the board is stable. Are you running the scripts in a step event each frame?
     
    Catch likes this.
  4. tagwolf

    tagwolf Member

    Joined:
    Aug 6, 2016
    Posts:
    59
    I uploaded my example project for you to look at. Please forgive the crappy assets but hopefully it'll help you out!
    https://host-a.net/f/226891-blox3-working-buyyz
     
    Catch likes this.
  5. Catch

    Catch Member

    Joined:
    Feb 11, 2019
    Posts:
    2
    Yeah, i was running it every step. Putting it in an alarm got my frame rate up to normal again. Thanks!
    it's funny though, I was trying it again this morning, running the script every step, and it wasn't nearly as bad as yesterday. Maybe there was some bad cache leftover.
     
    tagwolf likes this.
  6. Amon

    Amon Member

    Joined:
    Sep 13, 2016
    Posts:
    253
    This is a fantastic Tutorial. Thank you.
     
    tagwolf likes this.

Share This Page

  1. This site uses cookies to help personalise content, tailor your experience and to keep you logged in if you register.
    By continuing to use this site, you are consenting to our use of cookies.
    Dismiss Notice