2

I am making a program with some friends but we run into a problem in the code that often prevents us from testing it. We are making a Puzzle 15 game, we made a code in the separate "functions.c" file where we calculate the configuration inversions to check that the initial board has solution. But for some reason sometimes it still doesn't have a solution (we check all this with a separate program from the internet to solve the game automatically according to a given board).

We tried with several alternatives to calculate the inversions and the location of the empty space, but it keeps giving boards without solution (Note: It's seems that is more probable for the program to give solvable boards than unsolvable ones). I was hoping that you could help me to find the error so that the code only generates solvable configurations, thus facilitating the development when testing the game. Here is the code as reduced as possible:

// functions.c

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdbool.h>

#define ROWS 4
#define COLUMNS 4

// Print the matrix.
void printMatrix(int matrix[ROWS][COLUMNS]) {
    for (int i = 0; i < ROWS; i++) {
        for (int j = 0; j < COLUMNS; j++) {
            if (matrix[i][j] == 16) {
                printf("   ");  // Number 16 it's the empty space.
            } else {
                printf("%2d ", matrix[i][j]);
            }
        }
        printf("\n\n");
    }
}

// Function to count the inversions on the board.
int countInversions(int matrix[ROWS][COLUMNS]) {
    int inversions = 0;
    int elements[ROWS * COLUMNS];

    // Copy the matrix elements to a one-dimensional array.
    int k = 0;
    for (int i = 0; i < ROWS; i++) {
        for (int j = 0; j < COLUMNS; j++) {
            elements[k++] = matrix[i][j];
        }
    }

    // Count the inversions
    for (int i = 0; i < ROWS * COLUMNS - 1; i++) {
        for (int j = i + 1; j < ROWS * COLUMNS; j++) {
            if (elements[j] && elements[i] && elements[i] > elements[j]) {
                inversions++;
            }
        }
    }

    return inversions;
}

// Function to verify if the initial board configuration is resolvable.
bool itsSolvableConfiguration(int matrix[ROWS][COLUMNS]) {
    int inversions = countInversions(matrix);

    // Get the row containing the empty space.
    int rowEmptySpace = -1;
    for (int i = 0; i < ROWS; i++) {
        for (int j = 0; j < COLUMNS; j++) {
            if (matrix[i][j] == 16) {
                rowEmptySpace = i;
                break;
            }
        }
        if (rowEmptySpace != -1) {
            break;
        }
    }

    // Verify resolvability based on the position of the empty space and the number of inversions.
    return (rowEmptySpace % 2 == 0 && inversions % 2 == 0) || (rowEmptySpace % 2 != 0 && inversions % 2 != 0);
}

// Function to verify if the button entered by the user is valid.
bool itsValidChar(char button) {
    return (button == 'Y' || button == 'y' || button == 'S' || button == 's');
}



// main.c 

#include "functions.c"

int main(void) {
    int matrix[ROWS][COLUMNS];
    int numbers[16];
    int i, j, k, randomPivot;
    char answer;


    srand(getpid());

    printf("Puzzle 15\n\n");
    printf("CONTROLS:-Enter the number that is adjacent to the empty space to move it to the square.\n-Enter the key (Y) to restart or start the game.\n-Enter the key (S) to exit the game.");

    // Game start control - Board update.
    while (1) {
        printf("Load new game (Y to start / S to exit): ");
        scanf(" %c", &answer);
        for (int i = 0; i < 8; i++) {
                printf("\n");
        }

        if (!itsValidChar(answer)) {
            printf("\nInvalid key. Please choose a valid option.\n\n");
            continue;
        }

        if (answer == 'S' || answer == 's') {
            printf("\nThanks for playing!\n");
            break;
        }

        for (i = 0; i < 16; i++) {
            numbers[i] = i + 1;
        }

        // Randomizing positions with the Fisher-Yates algorithm.
        for (i = 16 - 1; i >= 0; i--) {
            j = rand() % (i + 1);
            randomPivot = numbers[i];
            numbers[i] = numbers[j];
            numbers[j] = randomPivot;
        }

        // The matrix is filled with random numbers.
        k = 0;
        for (i = 0; i < ROWS; i++) {
            for (j = 0; j < COLUMNS; j++) {
                matrix[i][j] = numbers[k++];
            }
        }

        // Verify if the initial board configuration is resolvable.
        if (!itsSolvableConfiguration(matrix)) {
            for (int i = 0; i < 15; i++) {
                printf("\n");
            }
            printf("The initial board configuration is not resolvable. Restart the game \nto obtain a valid configuration.");
            for (int i = 0; i < 12; i++) {
                printf("\n");
            }
        continue;
        }


        printf("Game started\n\n");

        for (int i = 0; i < 8; i++) {
                printf("\n");
        }

        // Function call to print the initial matrix.
        printMatrix(matrix);
        break;
    }

    return 0;
}
tomycero
  • 21
  • 1
  • In this excerpt ... `if (elements[j] && elements[i] && elements[i] > elements[j])`, what do you think the `elements[j] && elements[i] &&` part is doing for you? Because it appears to me that `elements[j]` and `elements[i]` will always be nonzero, so the overall condition is equivalent to just `elements[i] > elements[j]`. And maybe that's what you want, but if so, then you've chosen an odd way to express it. – John Bollinger Jul 07 '23 at 18:48
  • You represent the empty square with the number 16 and use it when counting inversions. This is not correct. – n. m. could be an AI Jul 07 '23 at 18:50
  • You need both the row and the column of the empty space to determine the correct parity. More accurately, you need the parity of the sum of the row and column displacements of the empty square. – Tom Karzes Jul 07 '23 at 19:18
  • @TomKarzes actually there are two equivalent ways of computing this: (1) using the permutation of all 16 positions and the sum of the row and column positions of the empty square; (2) using the permutation of just the 15 tiles (ignoring the empty square) and only the row of the empty square. This code seems to do a bit of this and a bit of that. – n. m. could be an AI Jul 07 '23 at 19:31
  • 1
    @n.m.willseey'allonReddit There are many ways to determine the parity. The point is that `itsSolvableConfiguration` hunts for the empty square, then ignores all but the row number. That's a bug. – Tom Karzes Jul 07 '23 at 19:42
  • https://www.geeksforgeeks.org/check-instance-15-puzzle-solvable/ – Dave Jul 07 '23 at 22:14

1 Answers1

0

I can't say whether this is your problem, but I looked up the rules for determining if a Puzzle 15 configuration is solvable just to make sure.

  • If the empty space is on an odd row counting from the bottom and the number of inversions is even, then the configuration is solvable.
  • If the empty space is on an even row counting from the bottom and the number of inversions is odd, then the configuration is solvable.

The key point in both of those conditions is "counting from the bottom." In your function itsSolvableConfiguration, your for loop starts at the top of your matrix. So if the empty space is on the top, then rowEmptySpace = 0. If it's on the second row, you get rowEmptySpace = 1. Then 2, then 3.

The easiest way to invert the behavior of your loop is to add a line after it, right before the function returns. This will effectively switch the perspective to counting from the bottom.

        ...
        break;
        }
    }
    // We count rows from the bottom, not from the top.
    rowEmptySpace = ROWS - rowEmptySpace;

    // Verify resolvability...
    return(rowEmptySpace...

I can't say if that will solve your problem, but it was a problem I saw. Hope it helps.

DeweyWoodz
  • 70
  • 6