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

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

Tags:
1. ### tagwolfMember

Joined:
Aug 6, 2016
Posts:
59
GM Version: GameMaker Studio 2.2.3
Target Platform: Windows / All
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.)

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(
"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) {

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) {

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) {

//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(
"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) {

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) {

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];

} else {

show_debug_message("NO HINTS AVAILABLE!");

}
```

Last edited: Aug 5, 2019
GDK and Catch like this.
2. ### CatchMember

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. ### tagwolfMember

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. ### tagwolfMember

Joined:
Aug 6, 2016
Posts:
59

Catch likes this.
5. ### CatchMember

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. ### AmonMember

Joined:
Sep 13, 2016
Posts:
277
This is a fantastic Tutorial. Thank you.

tagwolf likes this.