您的位置:首页 > 大数据 > 人工智能

一个迷宫生成算法

2006-07-26 11:11 330 查看
/*********************************************************************

The Algorithm
In a nutshell, the maze is created by starting with every room
completely sealed off, then knocking out walls until all rooms
are connected.

To be more precise, at any stage the maze is modelled by sets
of rooms - two rooms A and B are in the same set if there is a path
in the maze from room A to room B. Removing the wall between two rooms
is equivalent to taking the union of the sets containing them.
Initially each room is in a set by itself, the goal being to end up
with every room in a single set. A first draft of the algorithm
used is the following:

Put each room in a set by itself.
Pick a room at random, and pick a random wall within that room.
If this wall is "removable" then remove it, and join the sets
containing the two rooms which had that wall. If the wall is not
"removable", proceed to step 4.
Repeat from step 2 until only one set remains.

The important point about step 2 is that the wall chosen has to
be "removable". This means that it must not be a boundary wall
(one leading to a room outside the maze), it must not lead to a room
in the same set as the randomly chosen room (this guarantees
that there can only be one path between rooms) and it must satisfy
any constraints imposed by the options selected for the maze.
(The first two restrictions on "removable" walls are always imposed,
and so in the following discussion I won't mention them again.)

The easiest constraint to satisfy is that on diagonal connections
between rooms. If diagonal connections are not permitted, then
the algorithm above is unchanged; however, a "removable" wall
is considered to be one that connects rooms in a north/south,
east/west or up/down direction.

Slightly more difficult are the constraints (if imposed) on connections
between XY, XZ or YZ planes. For example, suppose the desired maze was
to be 3x3x3, constructed in such a way that it consisted of three 3x3
mazes stacked on top of each other with only one up/down connection
between floors. (This corresponds to setting ZConnections to 1.)
In this situation a wall that connects two rooms up/down is not
considered to be "removable" in the above algorithm, and the termination
condition must be altered to

4. Repeat from step 2 until only three sets remain.

When there are only three sets left these represent the three floors of
the maze, and all that remains is to knock out one wall connecting floors
one & two, and another connecting floors two & three:

5. Knock out walls to satisfy any constraints on connections between
XY, XZ or YZ planes.

Now there is only one set left, and at this stage any extra walls can
be removed to make the maze more open.

6. Knock out the specified number of extra walls to be removed at random.

Here's a second draft of the algorithm that the MazeGenerator libraries use:

Put each room in a set by itself.
Pick a room at random, and pick a random wall within that room.
If this wall is "removable" then remove it, and join the sets containing
the two rooms which had that wall. If the wall is not "removable",
proceed to step 4.
If there are no constraints on connections between planes then repeat
from step 2 until only one set remains. Otherwise, repeat from step 2
until the number of remaining sets is equal to the dimension of the
direction in which the constraints apply (think about it :)
If necessary, knock out walls to satisfy any constraints on connections
between XY, XZ or YZ planes.
Knock out the specified number of extra walls to be removed at random.

This is still a draft, because the algorithm as described above has
a major flaw. Exercise for the reader: determine what the flaw is and
how to fix it. (The MazeGenerator libraries use a slight modification
of the above algorithm that eliminates the flaw.)

*********************************************************************/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <time.h>

#ifdef WIN32
typedef char  int8_t;
#endif

struct floor_t;
struct room_t {
    struct room_t  *forward;
    struct room_t  *backward;
    struct floor_t *floor;
    int id;
    int8_t walls[4];
    int    closed_walls_num;
    struct room_t *next_room[4];
};

enum wall_status_t {
    Boundary=-1, Closed, Unremovable, Opened, Shut,
};
enum wall_alias {
    North=0, East=1, West=2, South=3,
};

// Here `floor` just represents the collection of `room`s
struct floor_t {
    struct room_t *forward, *backward;
};

struct building_t {
    struct floor_t link;
};

typedef struct room_t      room_t;
typedef struct floor_t     floor_t;
typedef struct building_t  building_t;

typedef enum { false, true } bool;

#define swap(a, b) do { /
    int t = a; a = b; b = t; /
}   while(0)

static int binary_search(int key, int array[], int count)
{
    int i;
    int front, back;

    front = 0; back = count-1;
    while(front <= back) {
        i = (front + back) / 2;
        if(key == array[i]) {
            return i;
        }
        else if(key < array[i])
            back = i-1;
        else
            front = i+1;

    }
    return -1;
}

static void remove_element(int index, int array[], int *count)
{
    const int arraysize = *count;
    if(index <0 || index >= arraysize)
        return;
    if(index < arraysize-1)
        memmove(&array[index], &array[index+1], (arraysize-index-1)*sizeof(int) );
    (*count)--;
}

static void init_floor(floor_t *floor)
{
    floor->forward = floor->backward = (room_t *) floor;
}

static void add_to_floor(floor_t *floor, room_t *room)
{
    room_t *here = floor->forward;

    room->forward = here->forward;
    here->forward = room;
    room->forward->backward = room;
    room->backward = here;
}

/****
static void join_floors(floor_t *floor, floor_t *floor2) // joint `floor2` into `floor`
{
    room_t *backward1 = floor->backward;
    room_t *forward2 = floor2->forward, *backward2 = floor2->backward;

    room_t *here = floor2->forward;
    for( ; here != floor2; here=here->forward) {
        here->floor = floor;
    }

    backward1->forward = forward2;
    forward2->backward = backward1;
    floor->backward = backward2;
    backward2->forward   = floor;
}
****/

static int *selectable_room_ids, *id2idx_table;
static int table_entry_num;

#define find_room(id)   id2idx_table[id]

static void update_tables(room_t *room)
{
    /*int i, num;
    for(i=0, num=0; i<4; i++)
        if(room->walls[i] == Closed)
            num++;
    if(num == 0) */
    if(room->closed_walls_num == 0) {
        const int index1 = find_room(room->id);
        if(index1 < table_entry_num) {
            const int index2 = --table_entry_num;
            const int id1 = room->id;
            const int id2 = selectable_room_ids[index2];

            swap(selectable_room_ids[index1], selectable_room_ids[index2]);
            id2idx_table[id1] = index2;
            id2idx_table[id2] = index1;
            //printf("room %d unselectable/n", room->id);
        }
    }
}

/**
static int get_closed_wall_num(room_t *room)
{
    int dir, num;
    for(dir=0, num=0; dir<4; dir++)
        if(room->walls[dir] == Closed)
            num++;
    return num;
}
**/

static room_t *get_next_room(room_t*, int);
static void join_floors(floor_t *floor, floor_t *floor2) // joint `floor2` into `floor`
{
    // update statuses of each room
    room_t *room = floor2->forward;
    for( ; room != (room_t *) floor2; room=room->forward) {
        int dir;
        if(room->closed_walls_num == 0)
            goto update_floor;
        for(dir=0; dir<4; dir++) {
            if(room->walls[dir]==Closed) {
                room_t *next_room = get_next_room(room, dir);
                //if(next_room && next_room->floor == floor) {
                if(next_room->floor == floor) { // in the same floor
                    room->walls[dir] = next_room->walls[3-dir] = Unremovable;
                    room->closed_walls_num--;
                    next_room->closed_walls_num--;
                    update_tables(next_room);
                }
            }
        }
        update_tables(room);
update_floor:
        room->floor = floor;
    }

    {
        room_t *backward1 = floor->backward;
        room_t *forward2 = floor2->forward, *backward2 = floor2->backward;
        backward1->forward = forward2;
        forward2->backward = backward1;
        floor->backward = backward2;
        backward2->forward   = (room_t *) floor;
        //floor2->forward = floor2->backward = (room_t *)floor2;
    }
}

static void init_building(building_t *bd)
{
    //bd->forward = bd->backward = &bd->link;
}

static void add_to_building(floor_t *floor, room_t *room)
{
    room_t *here = floor->forward;

    room->forward = here->forward;
    here->forward = room;
    room->forward->backward = room;
    room->backward = here;
}

static void dump_floor_forward(floor_t *floor)
{
    room_t *here = floor->forward;

    printf("floor:: ");
    for( ; here != (room_t *)floor; here=here->forward) {
        printf("room(%d), ", here->id);
    }
    printf("/n");
}

static void dump_floor_backward(floor_t *floor)
{
    room_t *here = floor->backward;

    printf("floor:: ");
    for( ; here != (room_t *)floor; here=here->backward) {
        printf("room(%d), ", here->id);
    }
    printf("/n");
}

static room_t *select_room(room_t rooms[])
{
    const int i = rand() % table_entry_num;
    return rooms + selectable_room_ids[i];
}

static int select_wall(room_t *room)
{
    int selectabe_walls_id[4], i, count;

    for(i=0, count=0; i<4; i++) {
        if(room->walls[i]==Closed) {
            selectabe_walls_id[count] = i;
            count++;
        }
    }
    //assert(count > 0);
    if(count == 0) {
        printf("room id: %d, east:%d, west:%d, north:%d, south:%d/n",
            room->id, room->walls[East], room->walls[West], room->walls[North], room->walls[South]);
        assert(count > 0);
    }
    i = rand()%count;
    return selectabe_walls_id[i];
}

/**
static room_t *get_next_room(room_t *all_rooms, int width, room_t *room, int dir)
{
    switch(dir) {
    case North: return room-width;
    case South: return room+width;
    case East:  return room+1;
    case West:  return room-1;
    }
    return NULL;
}
**/
static room_t *get_next_room(room_t *room, int dir)
{
    return room->next_room[dir];
}

static void knock_out_wall(room_t *room, int dir, room_t *next_room)
{
    room->walls[dir] = Opened;
    next_room->walls[3-dir] = Opened;
    room->closed_walls_num--;
    next_room->closed_walls_num--;

    update_tables(room);
    update_tables(next_room);
}

static floor_t *get_floor(room_t *room)
{
    return  (room->floor);
}

static void check_floor(floor_t *floor)
{
    // update statuses of each room
    room_t *room = floor->forward;
    for( ; room != (room_t *) floor; room=room->forward) {
        if(room->floor != floor) {
            fprintf(stderr, "check floor failed/n");
            exit(-1);
        }
    }
}

int create_maze(int width, int height, const char *filename, room_t **maze_rooms)
{
    int rv = -1;
    room_t  *rooms;
    floor_t *floors;
    const int size = width * height;
    int x, y, id, count;

    rooms  = (room_t *)  malloc(sizeof(room_t)  * size);
    floors = (floor_t *) malloc(sizeof(floor_t) * size);
    selectable_room_ids = (int *) malloc(sizeof(int) * size);
    id2idx_table = (int *) malloc(sizeof(int) * size);
    if(!rooms || !floors || !selectable_room_ids || !id2idx_table) {
        rv = -1;
        goto quit;
    }

    for(y=0, id=0; y<height; y++) {
        for(x=0; x<width; x++, id++) {
            rooms[id].id = id;
            rooms[id].closed_walls_num = 4;

            if(y > 0) {
                rooms[id].walls[North]     = Closed;
                rooms[id].next_room[North] = &rooms[id-width];
            } else {
                rooms[id].walls[North]     = Boundary;
                rooms[id].next_room[North] = NULL;
                --rooms[id].closed_walls_num;
            }
            if(y < height-1) {
                rooms[id].walls[South]     = Closed;
                rooms[id].next_room[South] = &rooms[id+width];
            } else {
                rooms[id].walls[South]     = Boundary;
                rooms[id].next_room[South] = NULL;
                --rooms[id].closed_walls_num;
            }
            if(x > 0) {
                rooms[id].walls[West]      = Closed;
                rooms[id].next_room[West]  = &rooms[id-1];
            } else {
                rooms[id].walls[West]      = Boundary;
                rooms[id].next_room[West]  = NULL;
                --rooms[id].closed_walls_num;
            }
            if(x < width-1) {
                rooms[id].walls[East]      = Closed;
                rooms[id].next_room[East]  = &rooms[id+1];
            } else {
                rooms[id].walls[East]      = Boundary;
                rooms[id].next_room[East]  = NULL;
                --rooms[id].closed_walls_num;
            }
            rooms[id].floor = &floors[id];

            init_floor(&floors[id]);
            add_to_floor(&floors[id], &rooms[id]);
            selectable_room_ids[id] = id2idx_table[id] = id;
        }
    }

    for(count = table_entry_num = size; count>1; count--)  {
        room_t *room = select_room(rooms);
        const int dir = select_wall(room);
        room_t *next_room = get_next_room(room, dir);
        floor_t *floor = get_floor(room), *floor2 = get_floor(next_room);

        knock_out_wall(room, dir, next_room);
        join_floors(floor, floor2);

        //check_floor(floor);
        /*-----//
        for(x=0; x<size; x++)
            dump_floor_forward(floors+x);
        printf("id:  ");
        for(x=0; x<table_entry_num; x++)
            printf("%d ", selectable_room_ids[x]);
        printf("/nidx: ");
        for(x=0; x<table_entry_num; x++)
            printf("%d ", id2idx_table[x]);
        printf("/n/n");
        //-----*/
    }

    if(filename)
    {
        FILE *fp = fopen(filename, "wb");
        if(!fp)
            goto quit;

        fwrite(&width,  1, 4, fp);
        fwrite(&height, 1, 4, fp);
        x = 0;
        fwrite(&x, 1, 4, fp);
        fwrite(&x, 1, 4, fp);
        for(y=0, id=0; y<height; y++) {
            for(x=0; x<width; x++, id++) {
                int dir;
                int8_t val = 0;
                for(dir=0; dir<4; dir++) {
                    if(rooms[id].walls[dir] != Opened)
                        val |= (1<<dir);
                }
                fwrite(&val, 1, sizeof(int8_t), fp);
            }
        }
        fclose(fp);
    }

    if(maze_rooms)
        *maze_rooms = rooms;

    rv = 0;
quit:
    if(rooms && !maze_rooms)  free(rooms);
    if(floors) free(floors);
    if(selectable_room_ids) free(selectable_room_ids);
    if(id2idx_table) free(id2idx_table);
    return rv;
}

int main1()
{
    room_t r1, r2, r3, r4, r5, r6;
    floor_t f1, f2;

    r1.id = 1, r2.id = 2, r3.id = 3, r4.id = 4, r5.id = 5, r6.id = 6;
    init_floor(&f1);
    init_floor(&f2);

    add_to_floor(&f1, &r1);
    add_to_floor(&f1, &r2);
    add_to_floor(&f1, &r3);
    dump_floor_forward(&f1);

    add_to_floor(&f2, &r4);
    add_to_floor(&f2, &r5);
    add_to_floor(&f2, &r6);
    dump_floor_forward(&f2);

//  join_floors(&f1, &f2);

    dump_floor_forward(&f1);
    dump_floor_backward(&f1);

    return 0;
}

typedef struct {
    void  *buf;
    unsigned int ptr;
    unsigned int end;
    int   elem_size;
    //int   elem_num;
}   stack_t;

static int init_stack(stack_t *s, int elem_size, int elem_num)
{
    s->buf = malloc(elem_size * elem_num);
    if(s->buf == NULL)
        return -1;
    s->ptr = (unsigned int) s->buf;
    s->end = s->ptr + elem_size * elem_num;
    s->elem_size = elem_size;

    return 0;
}

static void destroy_stack(stack_t *s)
{
    free(s->buf);
    memset(s, 0, sizeof(*s));
}

static void dump_stack( stack_t *s)
{
    char *p;

    for(p=s->buf; p!=(char *)s->ptr; p+=s->elem_size) {
        int val = *p;
        switch(s->elem_size) {
        case 0:  return;
        case 1:  val &= 0xff;      break;
        case 2:  val &= 0xffff;    break;
        case 3:  val &= 0xffffff;  break;
        }
        printf("%d ", val);
    }
    printf("/n");
}

static int push_stack(stack_t *s, void *elem)
{
    if(s->ptr == s->end)
        return -1;
    memcpy((void *)s->ptr, elem, s->elem_size);
    s->ptr += s->elem_size;
    //printf("%d --> ", *(int *)elem); dump_stack(s);
    return 0;
}

static int pop_stack(stack_t *s, void *elem)
{
    if((void *)s->ptr == s->buf)
        return -1;
    s->ptr -= s->elem_size;
    memcpy(elem, (void *)s->ptr, s->elem_size);
    //printf("%d <-- ", *(int *)elem); dump_stack(s);
    return 0;
}

bool go_thro_maze(room_t *rooms, int current, int goal, stack_t *s)
{
    room_t *room = &rooms[current];
    int dir;

    if(room->id == goal) {
        //printf("passed the maze successfully!/n");
        return true;
    }
    for(dir=0; dir<4; dir++) {
        if(room->walls[dir] == Opened) {
            room_t *next_room = room->next_room[dir];
            room->walls[dir] = next_room->walls[3-dir] = Shut; // close the door
            if(s)
                push_stack(s, &dir);
            if( go_thro_maze(rooms, next_room->id, goal, s))
                return true;
        }
    }
    if(s)
        pop_stack(s, &dir);
    return false;
}

static int check_solution(room_t *rooms, int start, int goal, stack_t *stk)
{
    room_t *room = &rooms[start];
    char *p;

    for(p=stk->buf; p!=(char *)stk->ptr; p++) {
        int dir = *p;
        if(room->walls[dir] != Shut)
            return -1;
        room = room->next_room[dir];
    }

    if(room->id != goal)
        return -1;
    return 0;
}

#ifdef WIN32
#include <windows.h>
#define get_tick_count  GetTickCount
#else
#define get_tick_count  clock
#endif

void test_maze()
{
    const unsigned t0 = get_tick_count();
    room_t *maze_rooms;
    stack_t stk;
    const int width  = rand()%200 + 2;
    const int height = rand()%200 + 2;
    const int size = width * height;
    int start, goal;

    start = rand() % size;
    while(1) {
        goal = rand() % size;
        if(goal != start)  break;
    }

    create_maze(width, height, NULL, &maze_rooms);
    if( init_stack(&stk, sizeof(char), size) < 0 ) {
        printf("init_stack failed/n");
        exit(-1);
    }
    if( !go_thro_maze(maze_rooms, start, goal, &stk) ) {
        printf("go_thro_maze() failed");
    }

//  dump_stack(&stk);
    if(check_solution(maze_rooms, start, goal, &stk) < 0)
        fprintf(stderr, "bad solution!/n");

    printf("test %dx%d maze, %d time units cost/n", width, height, get_tick_count()-t0 );

    free(maze_rooms);
    destroy_stack(&stk);
}

int main()
{
    srand(time(NULL));

    for(;;)
        test_maze();
    return 0;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息