Best way of Implementing a 3x3 sliding tile puzzle?

Started by Jleger91, Sun 16/03/2025 05:51:39

Previous topic - Next topic

Jleger91



I have a 3x3 sliding tile puzzle where objects slide into an empty space,
but it takes up more than 1500 lines of code and still doesn't work.
What is the most efficient and effective way to do this?

Code: ags
int rightX = 192;
  int middleX = 124;
  int leftX = 54;
  
  int bottomY = 198;
  int middleY = 132;
  int topY = 68;
  
  switch(tTopTile)
  {
    case "Top Left":
      switch(tMissingTile)
      {
        case "Right":
          tMissingTile = "Top Left";
          tTopTile = "Right";
          aTileSlide.Play();
          oTileTop.Move(rightX,  middleY,  4,  eBlock,  eAnywhere);
          break;
        case "Top":
          tMissingTile = "Top Left";
          tTopTile = "Top";
          aTileSlide.Play();
          oTileTop.Move(middleX,  topY,  4,  eBlock,  eAnywhere);
          break;
      }


Khris

The general approach is to first think about the best way to store the state of the puzzle. Here it's an array of size 9 where each element stores either 0 (the hole) or a number from 1 to 8 (the tiles).

The next thing you need is a function that displays this state on the screen. You use a loop to iterate over the array, calculate the coordinates based on the array index, then position / move the objects accordingly.
(This also requires some pre-planning, i.e. ideally having consecutive sprite slots and object IDs for the tiles).

The final thing you need is click handling. AGS supports using a single function to handle clicking every one of the eight objects. You can then use either the mouse coordinates and some math or the object that is passed to the handler function to figure out which tile was clicked.
If the click is valid (i.e. a tile next to the hole was clicked) you 1) update the game state 2) re-run the function that updates the screen.

Now let's do a "short" dive into the required array index math. The array will look something like this:
 [3, 6, 2, 0, 4, 1, 5, 7, 8]

I.e. the first row contains tiles #3, #6 and #2, the second row contains the hole, then tiles #4 and #1, etc.

When the player clicks an object, we can use the top left coordinates of the board and the tile size to calculate which one way clicked.
Code: ags
  int x = (mouse.x - top_left_coordinate) / tile_size; // 0, 1 or 2
If the coordinates are correct, x is now guaranteed to be 0, 1 or 2 because we clicked on object so we are inside the proper coordinate range by definition.
We can do the same for y and will also get a value from 0, 1 or 2.
Using y * 3 + x we can turn this into the array index (0-8).

Figuring out whether the hole is a neighbor is a bit more complicated. In general, checking the four cardinal directions means we're checking coordinates x,y-1 and x+1,y and x-1,y and finally x,y+1
Since you asked for the most efficient way, here it is:
Code: ags
  for (int di = 1; di <= 7; di += 2) {
    int dx = di % 3 - 1;
    int dy = di / 3 - 1;
    // neighboring coordinates xn/yn
    int xn = x + dx;
    int yn = y + dy;
    if (xn >= 0 && xn <=2 && yn >= 0 && yn <=2) { // neighbor is part of the board
      int ni = yn * 3 + xn; // calculate neighbor's array index
      if (board[ni] == 0) {  // if neighbor is the hole
        // swap values of board[clicked] and board[ni] i.e. clicked tile and hole
        // update the screen
        break; // exit the loop so we don't accidentally reverse the swap in a later loop
      }
    }
  }
This loop runs four times with di being 1, 3, 5 and 7. dx and dy will be 0/-1, -1/0, 1/0 and 0/1 respectively.
Thus xn/yn will be all four direct neighbors of x/y in terms of 2D board coordinates.

Jleger91

#3


Confusion setting up the puzzle based on Snarky's suggestion. Struggling to figure out what goes where.
Does any of this go in the global script or can it all be room script, etc?
Khris I tried your suggestion too but it was just as confusing.

EDIT: Using my own code of about 1500 lines I got it to work perfectly :D



Puzzle solved using said 1500 lines of code

Khris


Jleger91

#5


Solved it; not only does it look better visually but it uses way less code; kudos
Do you mind if I still use my original code and low-res graphics?

Khris

Not sure I understand the question; you're free to use whatever you like (including my code) :)

I get that posting code like mine is kind of useless unless it includes an in-depth explanation; I just wanted to show what is possible in general with regard to efficiency.

Crimson Wizard

#7
There's something that I'd like to say... Programming has two concepts: "scalability" and "reusability". In very simple words "scalability" means: how the solution behaves when the amount of data increases? and "reusability" naturally means if it's possible to use same code for many different variants of a task.

Your original code uses "switches" and does a separate check for each possible combination one by one. You say it requires 1500 lines, and that's just 3x3 puzzle. Imagine some time later you decide to make 5x5 puzzle, how much more lines will it take?
At the same time the variant of the code that uses arrays and "for" loops will increase only in the place where you initialize the board, but the main part will remain the same. That's because it handles each cell using same piece of code. And with a small number of changes it will be possible to adjust this code so that it can be used in any board size combination, any screen resolution, etc, so same code may be run from multiple puzzle rooms for example.

You may use any code you prefer of course, but I strongly recommend learning to write the variant with array and for loop whenever you have spare time. Learning efficient ways of coding takes time now but saves a huge amount of time later. Plus your program becomes compact, and compact programs are (usually) working faster and easier to fix in case of a mistake.

Jleger91

#8


Works better than ever; thank you Khris and Crimson Wizard

SMF spam blocked by CleanTalk