1
\$\begingroup\$

I am having a hard time identifying how to approach this problem.

If you have a match 3 game and the objects can only be swapped horizontally and vertically, and only swapped if it will result in a match of 3 or more jewels, which will be removed, with each removed jewel adding +1 to the score.

How would I find the best possible move? I'm confused at where to start and been searching for a while now and getting nowhere.

  • Board 6x6 or 8x8
  • Matches = 3 or more of same kind
  • write function to find best swap for maximum score.

Any help would be greatly appreciated !

EDIT: So I could iterate through the entire board with a nested for loop for the rows and columns, but im not sure what approach to take from here.

For example, say I start with top left which is "red" and I want to test if swapping this will result in a match. How would I go about searching and identifying all possibilities that would result in swapping this left equaling a match and score of 5.

**If I am being really unclear I apologies and will remove my question and possible reword.

Colour block example

\$\endgroup\$

3 Answers 3

3
\$\begingroup\$

Do you want the best move right now, or are you looking moves in advance?

If it's just the best move right now for the most points on this move, try every legal move and then pick the one with the highest value. Assume incoming gems during a chain reaction don't match with anything so you get the best you can calculate with no concern for what random gems might fall in to replace them.

If you want to look moves in advance, then you use the minimax algorithm on the above tactic. As you go more moves in advance, the unknown non-matching gems will take up more and more space, which mimics the inability to plan very far in advance in these games.

\$\endgroup\$
3
  • \$\begingroup\$ Thanks for your response! Its for the move right now, so which jewel if moved up,down,left or right, will return the greatest amount of points. Just confused how to implement the proper search. \$\endgroup\$
    – user144363
    Oct 16, 2020 at 14:42
  • \$\begingroup\$ Can you edit your question james, to show where you ran into trouble implementing the search? \$\endgroup\$
    – DMGregory
    Oct 16, 2020 at 14:43
  • \$\begingroup\$ Sure, apologies if im not being clear \$\endgroup\$
    – user144363
    Oct 16, 2020 at 15:03
0
\$\begingroup\$

Another approach is to brute force it. (Basically what Almo wrote, I will just explain the steps a bit more if you want only the best single move)

  1. Make a copy of your game board. You always only work with the copy, not the original board.
  2. You start at the top left tile. We are iterating now over the board, always checking to the right and bottom. You don't need to compare it to the left/ top since it would be a duplicated check.
  3. If your current tile is a valid move, check how many points you got for it. If it was your first valid move, save the tile and direction of your move and the score. If it is a second valid move, compare the score. If it is higher, replace your old saved tile/ direction/ score.
  4. After each valid made move, you grab another copy from the board and continue from the tile you checked last.
  5. You can also check multiple moves, like what is the max score you can make in 3 moves. All you need to to is change the copy you take in 1. from the result board you get after you made the move.

One thing you need to pay attention is, how are you generating new tiles. If you have some CreateRandomTile(), you should replace it by a list that gets prefilled by a few dozen tiles from your CreateRandomTile(). That way you guaranty that you always have the same board. (And it is easier compared to clone the random function). You won't be able to evaluate the absolut best for 10 moves ahead, but 3 moves ahead is all you need. When you find the best 3 next moves, just make the first of the three and reevaluate again the next 3.

\$\endgroup\$
2
  • \$\begingroup\$ Would you want to count future random tile drops in your prediction function? A human player wouldn't be able to predict that the next 2 tiles to drop will be blue, completing a combo that makes this low-scoring move actually high-scoring, so it might be "cheating" if the AI can see the future or peek into the dealer's deck that way. One small optimization you can make is to pre-scan your board and record with each tile the number of tiles of the same colour it's connected to. Then you can test if an initial move is valid and get its score in constant time, without repeated adjacency searches. \$\endgroup\$
    – DMGregory
    Oct 18, 2020 at 12:32
  • \$\begingroup\$ It depends on for what he needs the function. If it is a power up bonus, it would make sense that it could display the most powerful move including dropping tiles \$\endgroup\$
    – Zibelas
    Oct 19, 2020 at 7:15
0
\$\begingroup\$

I'm pretty sure this problem doesn't have a neat algorithm to it, so brute force is the way to go:

var optimalMove = {points: -1, x: null, y: null, direction: null}
for each (x) {
    for each (y) {
        for each (direction in [up, down, left, right]) {
            // Create identical copy of the board so we don't mess up the actual board
            var copy = realBoard.copy()
            copy.moveTile(x, y, direction)
            var points = copy.removeConnectedTiles()
            if (points > optimalMove.points)
                optimalMove = {points, x, y, direction}
        }
    }
}
realBoard.moveTile(optimalMove.x, optimalMove.y, optimalMove.direction)

Can be slightly optimized by not doing a move if the board would look identical, e.g:

if(tileAt(x, y).color == neighbor(x, y, direction).color) 
    continue

If you're having trouble finding which tiles are connected to which other tiles (so, finding the actual score) I would (off the top of my head...) look for the biggest Flood Fill. The algorithm is quite simple:

function floodFill(x, y, color = null, checked = []) {
    var tile = tileAt(x, y)
    if(color is null) 
        color = tile.color
    else if (tile.color is not color)
        return 0;
    
    var sum = 0
    if(checked does not contain {x + 1, y})
        sum += floodFill(x + 1, y, color, checked + {x + 1, y})
    if(checked does not contain {x - 1, y})
        sum += floodFill(x - 1, y, color, checked + {x - 1, y})
    if(checked does not contain {x, y + 1})
        sum += floodFill(x, y + 1, color, checked + {x, y + 1})
    if(checked does not contain {x, y - 1})
        sum += floodFill(x, y - 1, color, checked + {x, y - 1})
    return sum
}

var highest = {score: -1, x: null, y: null}
for each (x, y) {
    if (floodFill(x, y) > highest.score)
        highest = {score: floodFill(x, y), x, y}
}

My implementation might have some issues, check the linked wikipedia page. Can definitely be improved in performance (if a tile has already been checked, no need to check it again).

\$\endgroup\$

You must log in to answer this question.