• Hey Guest! Ever feel like entering a Game Jam, but the time limit is always too much pressure? We get it... You lead a hectic life and dedicating 3 whole days to make a game just doesn't work for you! So, why not enter the GMC SLOW JAM? Take your time! Kick back and make your game over 4 months! Interested? Then just click here!

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

tagwolf

Member
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:

Catch

Member
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
 

tagwolf

Member
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
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?
 

tagwolf

Member
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
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

Member
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?
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.
 
Top