Domanda

Given is a puzzle game with nine square cards.
On each of the cards there are 4 pictures at top, right, bottom and left.
Each picture on a card depicts either the front part or the rear part of an animal (a crocodile). Each picture has one of 5 colors.

Goal: to lay out the nine cards in a 3x3 grid in such a way that all "inner" (complete) crocodiles are properly combined with adjacent cards, i.e. have a front and rear end as well as matching colors.

To get a visual grip on the problem, here is a picture of the puzzle:

I found the depicted solution by hand.
Even though the puzzle looks simple at first glance, there is an extremely big number of combinations given that you can rotate each piece in 4 different ways.

The problem is now that I'd like to have an algorithm generating all possible 3x3 layouts in order to check all possible solutions (if there are any others). Preferably in Processing/Java.

Thoughts so far:
My approach would be to represent each of the 9 pieces by an array of 4 integer numbers, representing the 4 rotational states of a piece. Then generate all possible permutations of these 9 pieces, picking 1 of the 4 rotation-states from a piece array. A function isValidSolution() could then check a solution for violation of the constraints (color matching and front-rear matching).

Any ideas on how to implement this?

È stato utile?

Soluzione

It is possible to find all the solutions, trying not to explore all the unsuccessful paths of the search tree. The C++ code below, not highly optimized, finds a total of 2 solutions (that turn out to be the same unique solution because there is a duplicated tile, right answer?) almost instantaneously with my computer.

The trick here to avoid exploring all the possibilities is to call to function isValidSolution() while we are still placing the tiles (the function handles empty tiles). Also, to speed up the process, I follow a given order placing the tiles, starting in the middle, then the cross around it at left, right, top and bottom, and then the corners top-left, top-right, bottom-left and bottom-right. Probably other combinations give quicker executions.

It is of course possible to optimize this because of the special pattern distribution in this puzzle (the pattern with the letters only accepts one possible match), but that's beyond the scope of my answer.

#include<iostream>

// possible pattern pairs (head, body)
#define PINK    1
#define YELLOW  2
#define BLUE    3
#define GREEN   4
#define LACOSTE 5

typedef int8_t pattern_t; // a pattern is a possible color, positive for head, and negative for body
typedef struct {
    pattern_t p[4]; // four patterns per piece: top, right, bottom, left
} piece_t;

unsigned long long int solutionsCounter = 0;

piece_t emptyPiece = {.p = {0,  0,  0, 0} };

piece_t board[3][3] = {
    { emptyPiece, emptyPiece, emptyPiece},
    { emptyPiece, emptyPiece, emptyPiece},
    { emptyPiece, emptyPiece, emptyPiece},
    };

inline bool isEmpty(const piece_t& piece) {
    bool result = (piece.p[0] == 0);
    return result;
}

// check current solution
bool isValidSolution() {
    int i, j;
    for (i = 0; i < 2; i++) {
        for (j = 0; j < 3; j++) {
            if (!isEmpty(board[i][j]) && !isEmpty(board[i+1][j]) && (board[i][j].p[1] != -board[i+1][j].p[3])) {
                return false;
            }
        }
    }
    for (i = 0; i < 3; i++) {
        for (j = 0; j < 2; j++) {
            if (!isEmpty(board[i][j]) && !isEmpty(board[i][j+1]) && (board[i][j].p[2] != -board[i][j+1].p[0])) {
                return false;
            }
        }
    }
    return true;
}

// rotate piece
void rotatePiece(piece_t& piece) {
    pattern_t paux = piece.p[0];
    piece.p[0] = piece.p[1];
    piece.p[1] = piece.p[2];
    piece.p[2] = piece.p[3];
    piece.p[3] = paux;
}

void printSolution() {
    printf("Solution:\n");
    for (int i = 0; i < 3; i++) {
        for (int j = 0; j < 3; j++) {
            printf("\t  %2i  ", (int) board[j][i].p[0]);
        }
        printf("\n");
        for (int j = 0; j < 3; j++) {
            printf("\t%2i  %2i", (int) board[j][i].p[3], (int) board[j][i].p[1]);
        }
        printf("\n");
        for (int j = 0; j < 3; j++) {
            printf("\t  %2i  ", (int) board[j][i].p[2]);
        }
        printf("\n");
    }
    printf("\n");
}

bool usedPiece[9] = { false, false, false, false, false, false, false, false, false };
int colocationOrder[9] = { 4, 3, 5, 1, 7, 0, 2, 6, 8 };

void putNextPiece(piece_t pieces[9], int pieceNumber) {

    if (pieceNumber == 9) {
        if (isValidSolution()) {
            solutionsCounter++;
            printSolution();
        }
    } else {
        int nextPosition = colocationOrder[pieceNumber];
        int maxRotations = (pieceNumber == 0) ? 1 : 4; // avoids rotation symmetries.
        for (int pieceIndex = 0; pieceIndex < 9; pieceIndex++) {
            if (!usedPiece[pieceIndex]) {
                usedPiece[pieceIndex] = true;
                for (int rotationIndex = 0; rotationIndex < maxRotations; rotationIndex++) {
                    ((piece_t*) board)[nextPosition] = pieces[pieceIndex];
                    if (isValidSolution()) {
                        putNextPiece(pieces, pieceNumber + 1);
                    }
                    rotatePiece(pieces[pieceIndex]);
                }
                usedPiece[pieceIndex] = false;
                ((piece_t*) board)[nextPosition] = emptyPiece;
            }
        }
    }
}

int main() {

    // register all the pieces (already solved, scramble!)
    piece_t pieces[9] = {
        {.p = { -YELLOW,    -BLUE,     +GREEN,  +PINK} },
        {.p = { -YELLOW,    -GREEN,    +PINK,   +BLUE} },
        {.p = { -BLUE,      -YELLOW,   +PINK,   +GREEN }},
        {.p = { -GREEN,     -BLUE,     +PINK,   +YELLOW }},
        {.p = { -PINK,      -LACOSTE,  +GREEN,  +BLUE }},
        {.p = { -PINK,      -BLUE,     +GREEN,  +LACOSTE }},
        {.p = { -PINK,      -BLUE,     +PINK,   +YELLOW }},
        {.p = { -GREEN,     -YELLOW,   +GREEN,  +BLUE }},
        {.p = { -GREEN,     -BLUE,     +PINK,   +YELLOW }}
    };

    putNextPiece(pieces, 0);

    printf("found %llu solutions\n", solutionsCounter);

    return 0;
}

Altri suggerimenti

There are only 9 pieces, and thus each potential solution is representable by a small structure (say a 3x3 array of pieces, each piece with it's rotation), so the exact description of the pieces isn't too important.

Trying all the possible permutations is wasteful (to abuse LaTeX here, to place the 9 pieces on the grid can be done in $9!$ orders, as each one can be in 4 different orientations this gives a total of $9! \cdot 4^9 = 95126814720 \approx 10^{11}$, a bit too much to check them all). What you'd do by hand is to place a piece, say at the upper left side, and try to complete the square by fitting matching pieces into the rest. So you'd never consider any combinations where the first and second pieces don't match, cutting the search down considerably. This kind of idea is called backtracking. For it you need a description of the partial solution (the 3x3 grid with the filled in pieces and blank places, and the pieces not yet used; a specific order in which to fill the grid), a way of moving forward (place next piece if it fits, skip that one if it doesn't) and backwards (can't find any fits, undo last move and try the next possibility).

Obviously you have to design a way to find out if a potential match exists (given the filled in neighbors, try all orientations of a piece in it's asigned place). For such a small problem this probably isn't performance critical, but if you'd try to solve, say 100x100 the case is different...

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top