9/20/10

Just a quick update

So, I've finally got random dungeon generation working, although it could use a lot of tweaking (especially for getting those lovely long corridors!) and making corridors actually make sense. I'll include the source code in the end, so if you're interested you can have a look at it. Next up: including player character into the screen and enabling movement and collisions!

The platformer, on the other hand, progresses rather slow. I've just started implementing some kind of gravity to it, but it seems to be rather tricky from time to time. Hopefully it'll sort out in time. (Also, I suck at drawing so the game will most likely be hideous).



Oh, and just so that I don't forget, the code under is translated from Java example, that can be found here: http://roguebasin.roguelikedevelopment.org/index.php?title=Java_Example_of_Dungeon-Building_Algorithm
If you haven't tried to convert code from one language to another, you sure don't know what kind of pain it can be. Oh well.


#include <iostream>
#include <vector>
#include <ctime>
#include <cstdlib>


using namespace std;




class DungeonGenerator{
private:
    //defining maximum size of map:
    //150 columns and 40 rows
    int xMax;
    int yMax;


    //size of the map
    int xSize;
    int ySize;


    //number of "objects" to generate on the map
    int objects;
    // defining the chance-% to generate either a room
    // or a corridor on the map
    int roomChance;
    int corridorChance;


    //our map will be stored into a vector
    vector <int>dungeonMap;


    //a list of tile type's we'll be using
    static const int tileUnused;
    static const int tileStoneFloor;
    static const int tileStoneWall;
    static const int tileCorridor;
    static const int tileDoor;
    static const int tileStairsUp;
    static const int tileStairsDown;
    static const int tileChest;


    //misc. messages to print
    string msgXSize;
    string msgYSize;
    string msgMaxObjects;
    string msgNumObjects;
    string msgHelp;
    string msgDetailedHelp;


public:
    void setCell(int x, int y, int celltype);
    int getCell(int x, int y);
    int getRandomNumber(int min, int max);
    bool makeCorridor (int x, int y, int length, int direction);
    bool makeRoom (int x, int y, int xLength, int yLength, int direction);
    void printDungeon();
    bool createDungeon(int inx, int iny, int inobj);
    DungeonGenerator();
    ~DungeonGenerator();
};
    //defining the static variables
    const int DungeonGenerator::tileUnused = 0;
    const int DungeonGenerator::tileStoneFloor = 1;
    const int DungeonGenerator::tileStoneWall = 2;
    const int DungeonGenerator::tileCorridor = 3;
    const int DungeonGenerator::tileDoor = 4;
    const int DungeonGenerator::tileStairsUp = 5;
    const int DungeonGenerator::tileStairsDown = 6;
    const int DungeonGenerator::tileChest = 7;


DungeonGenerator::DungeonGenerator(){
    this->corridorChance = 25;
    this->roomChance = 75;
    this->objects = 0;
    this->xSize = 0;
    this->ySize = 0;
    this->xMax = 150;
    this->yMax = 40;
    this->msgXSize = "X-size of dungeon: \t";
    this->msgYSize = "Y-size of dungeon: \t";
    this->msgMaxObjects = "Max # of objects: \t";
    this->msgNumObjects = "# of objects made: \t";
    this->msgHelp = "";
    this->msgDetailedHelp = "";
    dungeonMap.resize(xMax * yMax);
}


DungeonGenerator::~DungeonGenerator(){
    
}




//setting a tile's type
void DungeonGenerator::setCell(int x, int y, int celltype){
    dungeonMap[x + xSize * y] = celltype;
}


int DungeonGenerator::getCell(int x, int y){
    return dungeonMap[x + xSize * y];
}


//here come's the RNG, Random Number God
//let's hope for the best!


int DungeonGenerator::getRandomNumber(int min, int max){
    int n = max - min + 2;


    int i = rand() % n;


    if (i < 0)
        i = -i;


    return min + i;
}


bool DungeonGenerator::makeCorridor (int x, int y, int length, int direction){


    //define the dimensions of the corridor


    int len = getRandomNumber(2, length);
    int floor = tileCorridor;
    int dir = 0;
    if (direction > 0 && direction < 4)
        dir = direction;


    int xTemp = 0;
    int yTemp = 0;


    switch(dir){
        case 0:
            //north
            //check if there's enough space for the corridor
            //start with checking it's not out of the boundaries


            if (x < 0 || x > xSize)
                return false;
            else xTemp = x;


            //same thing here, to make sure it won't go ootb
            for (yTemp = y; yTemp > (y - len); yTemp --){
                if (yTemp < 0 || yTemp > ySize)
                    return false; //oh shoot, it was!
                if (getCell(xTemp, yTemp) != tileUnused)
                    return false;
            }


            for (yTemp = y; yTemp > (y - len); yTemp --){
                setCell(xTemp, yTemp, floor);
            }


            break;


        case 1:
            //east


            if (y < 0 || y > ySize)
                return false;
            else
                yTemp = y;


            for (xTemp = x; xTemp < (x + len); xTemp++){
                if (xTemp < 0 || xTemp > xSize)
                    return false;
                if (getCell(xTemp, yTemp) != tileUnused)
                    return false;
            }


            for (xTemp = x; xTemp < (x + len); xTemp ++){
                setCell(xTemp, yTemp, floor);
            }


            break;


        case 2:
            //south
            if (x < 0 || x > xSize)
                return false;
            else
                xTemp = x;


            for (yTemp = y; yTemp < (y + len); yTemp ++){
                if (yTemp < 0 || yTemp > ySize)
                    return false;
                if (getCell(xTemp, yTemp) != tileUnused)
                    return false;
            }


            for(yTemp = y; yTemp < (y + len); yTemp ++){
                setCell(xTemp, yTemp, floor);
            }


            break;


        case 3:
            //west
            if (yTemp < 0 || yTemp > ySize)
                return false;
            else
                yTemp = y;


            for (xTemp = x; xTemp > (x - len); xTemp--){
                if (xTemp < 0 || xTemp > xSize)
                    return false;
                if (getCell(xTemp, yTemp) != tileUnused)
                    return false;
            }


            for (xTemp = x; xTemp > (x - len); xTemp--){
                setCell(xTemp, yTemp, floor);
            }


            break;
    }


    //we made it!
    return true;
}


bool DungeonGenerator::makeRoom(int x, int y, int xLength, int yLength, int direction){
    //defining the dimensions of the room
    //it should be at least 5x5


    int xlen = getRandomNumber(5, xLength);
    int ylen = getRandomNumber(5, yLength);


    //the tile type it's going to be filled with
    int floor = tileStoneFloor; //Stone Floor, obviously. Duh.
    int wall = tileStoneWall; //and stone walls, surprise!


    //choose the way it's pointing at


    int dir = 0;


    if (direction > 0 && direction < 4)
        dir = direction;


    switch (dir){
        case 0:
            //north


            //check if there's enough room left for it
            for(int yTemp = y; yTemp > (y - ylen); yTemp --){
                if(yTemp < 0 || yTemp > ySize)
                    return false;


                for (int xTemp = (x - xlen/2); xTemp < (x + (xlen + 1) / 2); xTemp ++){
                    if (xTemp < 0 || xTemp > xSize)
                        return false;
                    if (getCell(xTemp, yTemp) != tileUnused)
                        return false; //no space left
                }
            }


            //still alive, let us build a room!
            for (int yTemp = y; yTemp > (y - ylen); yTemp --){
                for (int xTemp = (x - xlen / 2); xTemp < (x + (xlen + 1) / 2); xTemp ++){
                    //let's begin from the walls
                    
                    if (xTemp == (x - xlen/2))
                        setCell(xTemp, yTemp, wall);
                    else if (xTemp == (x + (xlen - 1) / 2))
                        setCell(xTemp, yTemp, wall);
                    else if(yTemp == y)
                        setCell(xTemp, yTemp, wall);
                    else if(yTemp == (y - ylen + 1))
                        setCell(xTemp, yTemp, wall);
                    
                    //and then fill with the floor
                    
                    else setCell(xTemp, yTemp, floor);
                }
            }
            break;


        case 1:
            //east


            for (int yTemp = (y - ylen / 2); yTemp < (y + (ylen + 1) / 2); yTemp++){
                if (yTemp < 0 || yTemp > ySize)
                    return false;
                for (int xTemp = x; xTemp < (x + xlen); xTemp ++){
                    if (xTemp < 0 || xTemp > xSize)
                        return false;
                    if (getCell(xTemp, yTemp) != tileUnused)
                        return false;
                }
            }


            for (int yTemp = (y - ylen / 2); yTemp < (y + (ylen + 1) / 2); yTemp++){
                for (int xTemp = x; xTemp < (x + xlen); xTemp++){
                    if (xTemp == x)
                        setCell(xTemp, yTemp, wall);
                    else if(xTemp == (x + xlen - 1))
                        setCell(xTemp, yTemp, wall);
                    else if(yTemp == (y - ylen / 2))
                        setCell(xTemp, yTemp, wall);
                    else if(yTemp == (y + (ylen - 1) / 2))
                        setCell(xTemp, yTemp, wall);


                    else setCell(xTemp, yTemp, floor);
                }
            }
            break;


        case 2:
            //south


            for (int yTemp = yTemp; yTemp < (y + ylen); yTemp++){
                if (yTemp < 0 || yTemp > ySize)
                    return false;


                for (int xTemp = (x - xlen / 2); xTemp < (x + (xlen + 1) / 2); xTemp ++){
                    if (xTemp < 0 || xTemp > xSize)
                        return false;
                    if (getCell(xTemp, yTemp) != tileUnused)
                        return false;
                }
            }


            for (int yTemp = y; yTemp < (y + ylen); yTemp++){
                for (int xTemp = (x - xlen / 2); xTemp < (x + (xlen + 1) / 2); xTemp++){
                    if (xTemp == (x - xlen / 2))
                        setCell(xTemp, yTemp, wall);
                    else if (xTemp == (x + (xlen - 1) / 2))
                        setCell(xTemp, yTemp, wall);
                    else if (yTemp == y)
                        setCell(xTemp, yTemp, wall);
                    else if(yTemp == (y + ylen - 1))
                        setCell (xTemp, yTemp, wall);


                    else setCell(xTemp, yTemp, floor);
                }
            }
            break;


        case 3:
            //west


            for (int yTemp = (y - ylen / 2); yTemp < (y + (ylen + 1) / 2); yTemp++){
                if (yTemp < 0 || yTemp > ySize)
                    return false;
                for (int xTemp = x; xTemp > (x - xlen); xTemp --){
                    if (xTemp < 0 || xTemp > xSize)
                        return false;
                    if (getCell(xTemp, yTemp) != tileUnused)
                        return false;
                }
            }


            for (int yTemp = (y - ylen / 2); yTemp < (y + (ylen + 1) / 2); yTemp++){
                for (int xTemp = x; xTemp > (x - xlen); xTemp --){
                    if (xTemp == x)
                        setCell(xTemp, yTemp, wall);
                    else if(xTemp == (x - xlen + 1))
                        setCell(xTemp, yTemp, wall);
                    else if(yTemp == (y - ylen / 2))
                        setCell(xTemp, yTemp, wall);
                    else if(yTemp == (y + (ylen - 1) / 2))
                        setCell (xTemp, yTemp, wall);


                    else
                        setCell(xTemp, yTemp, floor);
                }
            }
            break;
    }
    //all done, finally!


    return true;
}




//for printing the dungeon for us to behold!
void DungeonGenerator::printDungeon(){


    for (int y = 0; y < ySize; y++){
        for (int x = 0; x < xSize; x++){


            switch(getCell(x, y)){
                case tileUnused:
                    cout << " ";
                    break;
                case tileStoneWall:
                    cout << "#";
                    break;
                case tileStoneFloor:
                    cout << ".";
                    break;
                case tileCorridor:
                    cout << "_";
                    break;
                case tileDoor:
                    cout << "+";
                    break;
                case tileStairsUp:
                    cout << "<";
                    break;
                case tileStairsDown:
                    cout << ">";
                    break;
                case tileChest:
                    cout << "*";
                    break;
            }
        }


        if (xSize < xMax)
            cout << endl;
    }
}


//for actually creating the dungeon
bool DungeonGenerator::createDungeon(int inx, int iny, int inobj){
    if (inobj < 1)
        objects = 10;
    else objects = inobj;


    //adjust the size of the map
    //if it's smaller or larger than the limits


    if (inx < 3)
        xSize = 3;
    else if(inx > xMax)
        xSize = xMax;
    else
        xSize = inx;


    if (iny < 3)
        ySize = 3;
    else if(iny > yMax)
        ySize = yMax;
    else
        ySize = iny;


    cout << msgXSize << "" << xSize << endl;
    cout << msgYSize << "" << ySize << endl;
    cout << msgMaxObjects << "" << objects << endl;


    //start with making the "standard stuff" on the map
    for (int y = 0; y < ySize; y++){
for (int x = 0; x < xSize; x++){
            //ie, making the borders of unwalkable walls
            if (y == 0)
                setCell(x, y, tileStoneWall);
            else if (y == ySize-1)
                setCell(x, y, tileStoneWall);
            else if (x == 0)
                setCell(x, y, tileStoneWall);
            else if (x == xSize-1)
                setCell(x, y, tileStoneWall);
            //and fill the rest with dirt
            else setCell(x, y, tileUnused);
}
    }




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


And now the code of the random-map-generation-algorithm begins!


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


    //start with making a room in the middle, which we can start building upon


    makeRoom(xSize/2, ySize/2, 8, 6, getRandomNumber(0,3));


    //keep count of the number of "objects" we've made
    int currentFeatures = 1; //+1 for the first room we just made


    //then we sart the main loop
    for (int countingTries = 0; countingTries < 1000; countingTries++){
        //check if we've reached our quota
        if (currentFeatures == objects)
            break;


        //start with a random wall
        int newX = 0;
        int xMod = 0;
        int newY = 0;
        int yMod = 0;
        int validTile = -1;


        //1000 chances to find a suitable object (room or corridor)..
        for (int testing = 0; testing < 1000; testing++){
            newX = getRandomNumber(1, xSize-1);
            newY = getRandomNumber(1, ySize-1);
            validTile = -1;
        
           if (getCell(newX, newY) == tileStoneWall || getCell(newX, newY) == tileCorridor){
                //check if we can reach the place
                if (getCell(newX, newY+1) == tileStoneFloor || getCell(newX, newY + 1) == tileCorridor){
                    validTile = 0;
                    xMod = 0;
                    yMod = -1;
                }
                else if (getCell(newX - 1, newY) == tileStoneFloor || getCell(newX - 1, newY) == tileCorridor){
                    validTile = 1;
                    xMod = +1;
                    yMod = 0;
                }
                else if (getCell(newX, newY - 1) == tileStoneFloor || getCell(newX, newY - 1) == tileCorridor){
                    validTile = 2;
                    xMod = 0;
                    yMod = +1;
                }
                else if (getCell(newX + 1, newY) == tileStoneFloor || getCell(newX + 1, newY) == tileCorridor){
                    validTile = 3;
                    xMod = -1;
                    yMod = 0;
                }
                //check that we haven't got another door nearby, so we won't get alot of openings besides
                //each other


                if (validTile > -1){
                    if (getCell(newX, newY + 1) == tileDoor) //north
                        validTile = -1;
                    else if (getCell(newX - 1, newY) == tileDoor)//east
                        validTile = -1;
                    else if (getCell(newX, newY - 1) == tileDoor)//south
                        validTile = -1;
                    else if (getCell(newX + 1, newY) == tileDoor)//west
                        validTile = -1;
                }


                //if we can, jump out of the loop and continue with the rest
                if (validTile > -1)
                    break;
                }
            }


            if (validTile > -1){


            //choose what to build now at our newly found place, and at what direction
            int feature = getRandomNumber(0, 100);
    
            if (feature <= roomChance){ //a new room
                if (makeRoom((newX + xMod), (newY + yMod), 8, 6, validTile)){
                    currentFeatures++; //add to our quota
                    //then we mark the wall opening with a door
                    setCell(newX, newY, tileDoor);
                    //clean up infront of the door so we can reach it
                    setCell((newX + xMod), (newY + yMod), tileStoneFloor);
                }
            }
            else if (feature >= roomChance){ //new corridor
                if (makeCorridor((newX + xMod), (newY + yMod), 6, validTile)){
                    //same thing here, add to the quota and a door
                    currentFeatures++;
                    setCell(newX, newY, tileDoor);
                }
            }
        }
    }


/*******************************************************************************
All done with the building, let's finish this one off
*******************************************************************************/
    //sprinkle out the bonusstuff (stairs, chests etc.) over the map


    int newX = 0;
    int newY = 0;
    int ways = 0; //from how many directions we can reach the random spot from
    int state = 0; //the state the loop is in, start with the stairs
    while (state != 10){
     for (int testing = 0; testing < 1000; testing++){
            newX = getRandomNumber(1, xSize - 1);
            newY = getRandomNumber(1, ySize - 2); //cheap bugfix, pulls down newy to 0<y<24, from 0<y<25
            ways = 4; //the lower the better
            //check if we can reach the spot
            if (getCell(newX, newY + 1) == tileStoneFloor || getCell(newX, newY + 1) == tileCorridor){
                //north
                if (getCell(newX, newY + 1) != tileDoor)
                    ways--;
            }
            if (getCell(newX - 1, newY) == tileStoneFloor || getCell(newX - 1, newY) == tileCorridor){
                //east
                if (getCell(newX - 1, newY) != tileDoor)
                    ways--;
            }
            if (getCell(newX, newY - 1) == tileStoneFloor || getCell(newX, newY - 1) == tileCorridor){
                //south
                if (getCell(newX, newY - 1) != tileDoor)
                    ways--;
            }
            if (getCell(newX + 1, newY) == tileStoneFloor || getCell(newX + 1, newY) == tileCorridor){
                //west
                if (getCell(newX + 1, newY) != tileDoor)
                    ways--;
            }


            if (state == 0){
                if (ways == 0){
                    //we're in state 0, let's place a "upstairs" thing
                    setCell(newX, newY, tileStairsUp);
                    state = 1;
                    break;
                }
            }
            else if (state == 1){
                if (ways == 0){
                    //state 1, place a "downstairs"
                    setCell(newX, newY, tileStairsDown);
                    state = 10;
                    break;
                }
            }
        }
    }


    //all done with the map generation, tell the user about it and finish


    cout <<msgNumObjects << "" << currentFeatures << endl;
    return true;
}


int main(){


    //initial stuff used in making the map
    int x = 80, y = 25, dungeon_objects = 20;


    //create a new object of the "DungeonGenerator"-class
    DungeonGenerator *generator = new DungeonGenerator();


    srand((unsigned)time(0));


    if (generator->createDungeon(x, y, dungeon_objects))
        generator->printDungeon();


    system("PAUSE");
}

No comments:

Post a Comment