/*
 * 箱入り娘を解く
 */

/* #define SYM */
/* #define MINUS2 */

#if 0
ABBC
ABBC
EFGH
IJJK
I  K

のような盤面から出発して、

****
****
****
*BB*
*BB*

(*は任意)のような状態を作ることが目的。
#endif

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

struct board {
    char a[5][4]; /* 盤面 */
    char b[5][4]; /* 盤面(同一形状のものを同一記号にしたもの) */
    int count; /* 何手で生成された盤面か? */
    struct board *prev; /* 盤面リストの前を指す */
    struct board *next; /* 盤面リストの次を指す */
    struct board *parent; /* この盤面の一手前の盤面を指す */
};


struct board *cur_node; /* 盤面リストの現在位置 */
struct board *last_node; /* 盤面リストの最後 */
int count; /* 現在の手数 */
int count2; /* 現在までに記憶されている盤面の数 */


/* 盤面を出力 */

void printboard(struct board *x)
{
    int i, j;

    printf("[%d]\n", x->count);
    for (i=0; i<5; i++) {
        for (j=0; j<4; j++) {
            printf("%c", x->a[i][j]);
        }
        printf("\n");
    }
}

/*
 * cで表されるblock(sizeはx×y)を上に動かす。
 * 可能ならば新盤面をmallocして返す。不可能ならNULLを返す。
 */

struct board *up_block(struct board *a, int x, int y, char c)
{
    int i, j, k, l;
    struct board *r;

    for (i=0; i<5-y; i++) {
        for (j=0; j<4-x+1; j++) {
            for (k=0; k<x; k++) {
                if (a->a[i][j+k] != ' ') goto next;
                for (l=0; l<y; l++) {
                    if (a->a[i+l+1][j+k] != c) goto next;
                }
            }
            r = (struct board *)malloc(sizeof(struct board));
            for (k=0; k<5; k++) {
                for (l=0; l<4; l++) {
                    r->a[k][l] = a->a[k][l];
                }
            }
            for (k=0; k<x; k++) {
                r->a[i][j+k] = c;
                r->a[i+y][j+k] = ' ';
            }
            r->count = a->count + 1;
            r->parent = a;
            return r;
            next:
        }
    }

    return NULL;
}

struct board *down_block(struct board *a, int x, int y, char c)
{
    int i, j, k, l;
    struct board *r;

    for (i=0; i<5-y; i++) {
        for (j=0; j<4-x+1; j++) {
            for (k=0; k<x; k++) {
                if (a->a[i+y][j+k] != ' ') goto next;
                for (l=0; l<y; l++) {
                    if (a->a[i+l][j+k] != c) goto next;
                }
            }
            r = (struct board *)malloc(sizeof(struct board));
            for (k=0; k<5; k++) {
                for (l=0; l<4; l++) {
                    r->a[k][l] = a->a[k][l];
                }
            }
            for (k=0; k<x; k++) {
                r->a[i][j+k] = ' ';
                r->a[i+y][j+k] = c;
            }
            r->count = a->count + 1;
            r->parent = a;
            return r;
            next:
        }
    }

    return NULL;
}

struct board *left_block(struct board *a, int x, int y, char c)
{
    int i, j, k, l;
    struct board *r;

    for (i=0; i<5-y+1; i++) {
        for (j=0; j<4-x; j++) {
            for (k=0; k<y; k++) {
                if (a->a[i+k][j] != ' ') goto next;
                for (l=0; l<x; l++) {
                    if (a->a[i+k][j+l+1] != c) goto next;
                }
            }
            r = (struct board *)malloc(sizeof(struct board));
            for (k=0; k<5; k++) {
                for (l=0; l<4; l++) {
                    r->a[k][l] = a->a[k][l];
                }
            }
            for (k=0; k<y; k++) {
                r->a[i+k][j] = c;
                r->a[i+k][j+x] = ' ';
            }
            r->count = a->count + 1;
            r->parent = a;
            return r;
            next:
        }
    }

    return NULL;
}

struct board *right_block(struct board *a, int x, int y, char c)
{
    int i, j, k, l;
    struct board *r;

    for (i=0; i<5-y+1; i++) {
        for (j=0; j<4-x; j++) {
            for (k=0; k<y; k++) {
                if (a->a[i+k][j+x] != ' ') goto next;
                for (l=0; l<x; l++) {
                    if (a->a[i+k][j+l] != c) goto next;
                }
            }
            r = (struct board *)malloc(sizeof(struct board));
            for (k=0; k<5; k++) {
                for (l=0; l<4; l++) {
                    r->a[k][l] = a->a[k][l];
                }
            }
            for (k=0; k<y; k++) {
                r->a[i+k][j] = ' ';
                r->a[i+k][j+x] = c;
            }
            r->count = a->count + 1;
            r->parent = a;
            return r;
            next:
        }
    }

    return NULL;
}


/* 同一形状のblockを同一視するための下請け */

char change(char c)
{
    if ((c == 'C') || (c == 'I') || (c == 'K')) return 'A';
    if ((c == 'F') || (c == 'G') || (c == 'H')) return 'E';
    return c;
}

/* 比較用の盤面を生成 */

void make_board_for_compare(struct board *x)
{
    int i, j;
    int k, l;
    int tmp;

    for (i=0; i<5; i++) {
        for (j=0; j<4; j++) {
            x->b[i][j] = change(x->a[i][j]);
        }
    }
#ifdef SYM
    for (i=0; i<5; i++) {
        for (j=0; j<4; j++) {
            if (x->b[i][j] > x->b[i][3-j]) return;
            else if (x->b[i][j] < x->b[i][3-j]) {
                /* 左右反転 */
                for (k=0; k<5; k++) {
                    for (l=0; l<2; l++) {
                        tmp = x->b[k][l];
                        x->b[k][l] = x->b[k][3-l];
                        x->b[k][3-l] = tmp;
                    }
                }
                return;
            }
        }
    }
#endif SYM
}

/* 同一の盤面が過去に発見されていたか調べる */

int check(struct board *x)
{
    struct board *p;
    int i, j;

    make_board_for_compare(x);

    p = last_node;

    while (1) {
        if (p == NULL) break;
#ifdef MINUS2
        if (p->count < x->count - 2) break;
#endif
        for (i=0; i<5; i++) {
            for (j=0; j<4; j++) {
                if (p->b[i][j] != x->b[i][j]) goto next;
            }
        }
        return 0;
        next:
        p = p->prev;
    }

    return 1;
}

/* xが新盤面ならリストに追加、そうでなければfreeする */
/* xがgoalならば履歴を出力し、program終了 */

void check_append(struct board *x)
{
    if (x == NULL) return;

    if (check(x) == 1) {
        last_node->next = x;
        x->prev = last_node;
        x->next = NULL;
        last_node = x;
        
        count2++;
        /* printboard(x); */
        if (x->count != count) {
            count = x->count;
            printf("Searching states for %d steps (We already have %d states)\n", count, count2);
        }

        if ((x->a[3][1] == 'B')
            && (x->a[3][2] == 'B')
            && (x->a[4][1] == 'B')
            && (x->a[4][2] == 'B')
           ) {
            printf("solution found.\n");
            while (1) {
                printboard(x);
                x = x->parent;
                if (x == NULL) break;
            }
            exit(0);
        }
    } else {
        free(x);
    }
}

/*
 * cで表されるblock(sizeはx×y)を動かして出来る新盤面をリストに追加。
 */

void move_block(struct board *b, int x, int y, char c)
{
    struct board *tmp;

    tmp = up_block(b, x, y, c); 
    check_append(tmp);
    tmp = down_block(b, x, y, c); 
    check_append(tmp);
    tmp = left_block(b, x, y, c); 
    check_append(tmp);
    tmp = right_block(b, x, y, c); 
    check_append(tmp);
}

main()
{
    /* 初期盤面 */
    struct board first = {
        {"ABBC",
         "ABBC",
         "EFGH",
         "IJJK",
         "I  K"
        }, /* a */
        {"ABBA",
         "ABBA",
         "EEEE",
         "AJJA",
         "A  A"
        }, /* b */
        0, /* count */
        NULL, /* prev */
        NULL, /* next */
        NULL /* parent */
    };

    cur_node = &first;
    last_node = &first;
    count = 0;
    count2 = 1;

    while (1) {
        move_block(cur_node, 1, 2, 'A'); 
        move_block(cur_node, 2, 2, 'B'); 
        move_block(cur_node, 1, 2, 'C'); 
        move_block(cur_node, 1, 1, 'E'); 
        move_block(cur_node, 1, 1, 'F'); 
        move_block(cur_node, 1, 1, 'G'); 
        move_block(cur_node, 1, 1, 'H'); 
        move_block(cur_node, 1, 2, 'I'); 
        move_block(cur_node, 2, 1, 'J'); 
        move_block(cur_node, 1, 2, 'K'); 

        cur_node = cur_node->next;
        if (cur_node == NULL) break;
    }
    printf("solution not found.\n");
}
