Storing coordinates of multiple creatures

This is a small program that moves the S around the map randomly.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
#include <iostream>
#include <windows.h>
#include <cstdlib>
#include <ctime>

void MoveCursorHome()
{
    HANDLE  hStdOut;
    COORD   homeCoords = { 0, 0 };
    hStdOut = GetStdHandle( STD_OUTPUT_HANDLE );

    if (hStdOut == INVALID_HANDLE_VALUE)
        return;

    /* Move the cursor home */
    SetConsoleCursorPosition( hStdOut, homeCoords );
}

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

    struct position
    {
        int CurrentX, CurrentY;
        int NewX, NewY;
    } Enemy;

char (* Map)[20][40];
char map1[20][40] =
{"#######################################",
 "#                                     #",
 "#                                     #",
 "#                  S                  #",
 "#     S                               #",
 "#                                     #",
 "#                                     #",
 "#                                     #",
 "#            S             S          #",
 "#                                     #",
 "#                                     #",
 "#                        S            #",
 "#     S                               #",
 "#                                     #",
 "#######################################"};

short random = 0;
Map = &map1;

 while(true)
 {
    MoveCursorHome();
    for (int i = 0; i < 20; i++)
    {
    std::cout << (* Map)[i] << "\n";
    }

    for (Enemy.CurrentY = 0; Enemy.CurrentY < 20; Enemy.CurrentY++)
    {
    for (Enemy.CurrentX = 0; Enemy.CurrentX < 40; Enemy.CurrentX++)
    {
        switch ((* Map)[Enemy.CurrentY][Enemy.CurrentX])
        {
            case 'S':
            Enemy.NewX = Enemy.CurrentX, Enemy.NewY = Enemy.CurrentY;
            random = rand() % 4 + 1;
            if (random == 1) {Enemy.NewY -= 1;}
            if (random == 2) {Enemy.NewY += 1;}
            if (random == 3) {Enemy.NewX -= 1;}
            if (random == 4) {Enemy.NewX += 1;}

                switch((*Map)[Enemy.NewY][Enemy.NewX])
                {
                    case ' ':
                    (*Map)[Enemy.CurrentY][Enemy.CurrentX] = ' ';
                    Enemy.CurrentX = Enemy.NewX; Enemy.CurrentY = Enemy.NewY;
                    (*Map)[Enemy.NewY][Enemy.NewX] = 'S';
                    break;

                    default:
                    random = 0;
                    break;
                }
            break;
        }
    }
    }

    Sleep(200);
 }

return 0;
}


Some people have told me, that instead of searching the whole map for enemies, I should store their co-ordinates in an array or in a vector. How?
And can I please have an example for this specific code?
1
2
3
4
5
6
7
8
9
10
11
12
struct Enemy {
    int x;
    int y;
}

//...
vector<Enemy> enemies;
//...

for(Enemy& : enemies) {
    //Do something with each enemy
}
Last edited on
You mean something like that?
1
2
3
4
5
6
7
8
9
10
11
12
class Entity
{
    public:
    int CurrentX, CurrentY;
    int NewX, NewY;
};

class Enemy : public Entity
{
    public:
    std::vector<Enemy> enemies;
};

(This doesn't work, I put it there, just so you could tell me if I'm completely wrong)

One does not simply write:
struct Enemy {
int x;
int y;
}
Last edited on
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Entity
{
    public:
    int CurrentX, CurrentY;
    int NewX, NewY; //Not needed unil you need to queue movement
};

class Enemy : public Entity
{
    public:
    std::vector<Enemy> enemies;
};
//↑Bad idea. Better to do something like:

class Map
{
public:
char** map;//Do not forget to initialize it in constructor
int size_x, size_y; //same
std::vector<Entity> enemies;
Entity player;
}

Map "has a" player and enemies
http://en.wikipedia.org/wiki/Has-a
Ok that gets a lot more confusing if you have, let's say, two maps. Should I create separate container classes for both and then make a pointer that first points to the first map and then (after my condition has been fulfilled) changes to the second one?

And the compiler gives me the errors:
main.cpp:53:27: error: template argument for 'template<class _Alloc> class std::allocator' uses local type 'main()::Entity'
main.cpp:53:27: error: trying to instantiate 'template<class _Alloc> class std::allocator'
main.cpp:53:27: error: template argument 2 is invalid

That's the line where std::vector<Entity> enemies; is in the class Map.
I hope you didn't declared those classes inside main()?
You can create custom constructor for map:
1
2
3
4
Map::Map(char** map_, x_size, y_size) : size_x(x_size), size_y(y_size)
{
    //Do deep copy of map_ to map here.
}

And then create two Maps:
1
2
3
4
5
6
7
8
9
10
char map1[5][5] = {"#####",
                   "#   #",
                   "# ! #",
                   "#   #",
                   "#####"}
char map2[3][3] = {"###",
                   "# #",
                   "###"}
Map first(map1, 5, 5);
Map second(map2, 3, 3);
I hope you didn't declare those classes inside main()?
LOL, I'm stupid :D
Thanks, for pointing that out.

I'll try your suggestions and see what I can come up with.
This may be going in a slightly different direction to the previous comments. It's a re-working of the original program using a vector for the enemies (blobs in this code). I also took out the literal values such as 20 and 40 and replaced them with HEIGHT and WIDTH. This allows easy modification if required.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
#include <iostream>
#include <windows.h>
#include <cstdlib>
#include <ctime>
#include <vector>
#include <string>

const int HEIGHT = 15;
const int WIDTH  = 40;
//const int HEIGHT = 25;
//const int WIDTH  = 60;

struct Blob {
    int x;
    int y;
    Blob(int a, int b) :x(a), y(b) {}
};

void MoveCursorHome()
{
    HANDLE  hStdOut;
    COORD   homeCoords = { 0, 0 };
    hStdOut = GetStdHandle( STD_OUTPUT_HANDLE );

    if (hStdOut == INVALID_HANDLE_VALUE)
        return;

    /* Move the cursor home */
    SetConsoleCursorPosition( hStdOut, homeCoords );
}

void makeMap(char map[HEIGHT][WIDTH], std::vector<Blob> &blobs);
void outputMap(char map[HEIGHT][WIDTH]);
void moveBlobs(std::vector<Blob> &);
void createBlobs(std::vector<Blob> & blobs, int h, int w, int count);


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

    std::vector<Blob> blobs;
    createBlobs(blobs, HEIGHT, WIDTH, 6);

    char (* Map)[HEIGHT][WIDTH];
    char map1[HEIGHT][WIDTH];

    Map = &map1;

    while(true)
    {
        MoveCursorHome();
        makeMap(*Map, blobs);
        outputMap(*Map);
        moveBlobs(blobs);
        Sleep(200);
    }

    return 0;
}

void makeMap(char map[HEIGHT][WIDTH], std::vector<Blob> &blobs)
{

//    static const char one[] = "#######################################";
//    static const char two[] = "#                                     #";
    static const std::string one(WIDTH-1, '#');
    static const std::string two = "#" + std::string(WIDTH-3, ' ') + "#";

    strcpy(map[0],one.c_str());
    strcpy(map[HEIGHT-1],one.c_str());
    for (int i=1; i<HEIGHT-1; i++)
        strcpy(map[i],two.c_str());

    for (int i=0; i<blobs.size(); i++)
    {
        map[blobs[i].y][blobs[i].x] = 'S';
    }
}

void outputMap(char map[HEIGHT][WIDTH])
{
    for (int i = 0; i < HEIGHT; i++)
        std::cout << map[i] << "\n";
}

void moveBlobs(std::vector<Blob> & blobs)
{
    for (int i=0; i<blobs.size(); i++)
    {
        int random = rand() % 4 + 1;

        if ((random == 1) && (blobs[i].y > 1))
            blobs[i].y--;
        else if ((random == 2) && (blobs[i].y <HEIGHT-2))
            blobs[i].y++;
        else if ((random == 3) && (blobs[i].x > 1))
            blobs[i].x--;
        else if ((random == 4) && (blobs[i].x < WIDTH-3))
            blobs[i].x++;
    }
}

void createBlobs(std::vector<Blob> & blobs, int h, int w, int count)
{
    // create object with coords in range
    //    x = 1 to h - 1
    //    y = 1 to w - 2

    for (int i=0; i<count; i++)
    {
        int x = rand()% (w-3) + 1;
        int y = rand()% (h-2) + 1;
        blobs.push_back(Blob(x,y));
    }
}
Last edited on
I agree with your advice to replace numbers by HEIGHT and WIDTH, but why did you make creating maps so complicated? They're going to be defined by the programmer anyway, it's not like you created an algorithm with many kinds of patterns that can generate random maps.
In this case defining maps like this:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
char map1[HEIGHT][WIDTH] =
{"#######################################",
 "#                                     #",
 "#                                     #",
 "#                  S                  #",
 "#     S                               #",
 "#                                     #",
 "#                                     #",
 "#                                     #",
 "#            S             S          #",
 "#                                     #",
 "#                                     #",
 "#                        S            #",
 "#     S                               #",
 "#                                     #",
 "#######################################"};

is waaaaay easier! It is so, unless I am completely mistaken and your code does something that I don't know, or it is because I'm a beginner and I didn't understand something.

What do you mean with the lines you commented out? Like these two:
// static const char one[] = "#######################################";
// static const char two[] = "# #";


P.S. Oooh, I see - you generate random locations for the enemies - that's interesting, but what if there are a lot more objects that the enemies can't spawn in (this also applies to the movement function as they cannot collide with other objects as well), not just walls (in my full game I have trees, potions, traps, the player of course...)

P.P.S. S is for Snakes :D
I do agree that I made creating the maps complicated.

It was just me following a train of thought, that is, how to make the size of the map bigger or smaller in a very easy way. So yes, my code is complicated, but just change HEIGHT or WIDTH to a bigger or smaller number and everything automatically scales to the new dimensions.

I understand that may not be what you want, so feel free to ignore it :)

The lines commented out such as this:
1
2
//    static const char one[] = "#######################################";
//    static const char two[] = "#                                     #"; 

I left them there partly as a left-over of an earlier version of the code, and partly as an explanatory comment to give a hint as to what the next two lines of the new code are doing.

As for other objects or obstacles, I suppose you could create a separate array or vector containing their coordinates, but it might be simpler to place them on the map and test whether that position is occupied before creating the new enemy.
Oh so you mean I should spawn enemies into the map (this way I'll have their co-ordinates when they're spawned).
Because at first my question was how to store the co-ordinates of enemies after you put them on the map yourself.

I think the idea of spawning them is way better, I just need to play around with the techniques to see if there's a way to tell the enemies that they can only spawn on empty spaces and not anywhere else, instead of telling them where they can't spawn :P
If you know how to do that, please tell me.
@ Chervil
I liked your code a lot, so I modified it slightly, later I'll try to implement it into my main project.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
#include <iostream>
#include <cstdlib>
#include <ctime>
#include <vector>
#include <string>
#include <windows.h>

const unsigned short HEIGHT = 15;
const unsigned short WIDTH  = 40;

struct Snake {
    unsigned short x, y;
    Snake(unsigned short a, unsigned short b) :x(a), y(b) {}
};

void MoveCursorHome()
{
    HANDLE  hStdOut;
    COORD   homeCoords = { 0, 0 };
    hStdOut = GetStdHandle( STD_OUTPUT_HANDLE );

    if (hStdOut == INVALID_HANDLE_VALUE)
        return;

    /* Move the cursor home */
    SetConsoleCursorPosition( hStdOut, homeCoords );
}

void makeMap(char map[HEIGHT][WIDTH], std::vector<Snake> &snakes)
{
    static const std::string one(WIDTH-1, '#');
    static const std::string two = "#" + std::string(WIDTH-3, ' ') + "#";

    strcpy(map[0],one.c_str());
    strcpy(map[HEIGHT-1],one.c_str());
    for (unsigned short index = 1; index < HEIGHT - 1; index++)
        strcpy(map[index],two.c_str());

    for (unsigned short index = 0; index < snakes.size(); index ++)
    {
        map[snakes[index].y][snakes[index].x] = 'S';
    }
}

void outputMap(char map[HEIGHT][WIDTH])
{
    for (unsigned short index = 0; index < HEIGHT; index++)
        std::cout << map[index] << "\n";
}

void moveSnakes(std::vector<Snake> & snakes)
{
    for (unsigned short index = 0; index < snakes.size(); index ++)
    {
        unsigned short random = rand() % 16 + 1;

        if ((random == 1) && (snakes[index].y > 1))
            snakes[index].y--;
        else if ((random == 2) && (snakes[index].y <HEIGHT-2))
            snakes[index].y++;
        else if ((random == 3) && (snakes[index].x > 1))
            snakes[index].x--;
        else if ((random == 4) && (snakes[index].x < WIDTH-3))
            snakes[index].x++;
    }
}

void spawnSnakes(std::vector<Snake> & snakes, unsigned short h, unsigned short w, unsigned short amount)
{
    // Create object with coords in range
    // x = 1 to h - 1
    // y = 1 to w - 2

    for (unsigned short index = 0; index < amount; index ++)
    {
        unsigned short x = rand()% (w-3) + 1;
        unsigned short y = rand()% (h-2) + 1;
        snakes.push_back(Snake(x,y));
    }
}

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

    std::vector<Snake> snakes;
    spawnSnakes(snakes, HEIGHT, WIDTH, 6);

    char (* Map)[HEIGHT][WIDTH];
    char map1[HEIGHT][WIDTH];

    Map = &map1;

    while(true)
    {
        MoveCursorHome();
        makeMap(*Map, snakes);
        outputMap(*Map);
        moveSnakes(snakes);
        Sleep(100);
    }

    return 0;
}


This code doesn't produce warnings (yours, when comparing the size of the vector with another value, warned about comparison between signed and unsigned integers).
If I made a mistake with changing the code that way, please tell me.
This code doesn't produce warnings (yours, when comparing the size of the vector with another value, warned about comparison between signed and unsigned integers).

Yes, I overlooked that, it depends which compiler (and settings) I'm using.
Your code is ok, though you might just put unsigned. To be strictly correct in comparing like with like, this would be more accurate:
for (std::vector<Snake>::size_type index = 0; index < snakes.size(); index ++)
in order to match the return type of the .size() function.


After posting my previous code, I thought a little bit more. The code in the function moveSnakes() checks that the snakes don't move outside the boundaries of the playing area. This is a very safe way of dealing with things. However since the playing area has a border made of the '#' character, it might be useful to simply test that the new position on the board contains a space character. This would account for not just the borders, but any other obstacles or objects.

In addition, the same idea could be used in the initial spawnSnakes() function, in order to avoid landing on top of an obstacle. This would mean building the map first, complete with obstacles.
Last edited on
I thought about making a function for moving the snakes, which would resemble my old method, but it would spawn the snakes and it should look something like this:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
void moveSnakes(char map[HEIGHT][WIDTH], std::vector<Snake> & snakes)
{
    for (std::vector<Snake>::size_type index = 0; index < snakes.size(); index ++)
    {
        map[snakes[index].y][snakes[index].x] = 'S';
        snakes[index].new_x = snakes[index].x;
        snakes[index].new_y = snakes[index].y;
        unsigned short random = rand() % 16 + 1;
        if (random == 1) {snakes[index].new_y -= 1;}
        if (random == 2) {snakes[index].new_y += 1;}
        if (random == 3) {snakes[index].new_x -= 1;}
        if (random == 4) {snakes[index].new_x += 1;}

        switch(map[snakes[index].new_y][snakes[index].new_x])
        {
            case ' ':
            map[snakes[index].y][snakes[index].x] = ' ';
            snakes[index].x = snakes[index].new_x;
            snakes[index].y = snakes[index].new_y;
            map[snakes[index].new_y][snakes[index].new_x] = 'S';
            break;

            default:
            random = 0; //Resets the enemy movement back to 0
            break;
        }
    }
}

This works, however I just posted this as an idea, but can you tell me how I should change the other parts of the code accordingly, you see I think that I can spawn the snakes and start moving them in one function :)

Random is 1 out of 16 to make the snakes' movements even less predictable.
Last edited on
Everything is fine, but I want to make a good spawn function as well!
This is how far I got:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
void spawnSnakes(char map[HEIGHT][WIDTH], std::vector<Snake> & snakes, unsigned short amount)
{
    for (unsigned short index = 0; index < amount; index ++)
    {
        generatelocation:
        unsigned short x = rand()% (WIDTH - 3) + 1;
        unsigned short y = rand()% (HEIGHT - 2) + 1;

        switch(map[y][x])
        {
            case ' ':
            snakes.push_back( Snake(x,y) );
            break;

            default:
            x = 0, y = 0;
            goto generatelocation;
        }
    }
}


I hate using goto, but I spent a long time trying to do it in different ways and this is the only one that worked.
There isn't actually any law against using goto, but usually I find the code without it is clearer and easier to understand.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
void spawnSnakes(char map[HEIGHT][WIDTH], std::vector<Snake> & snakes, unsigned short amount)
{
    for (unsigned short index = 0; index < amount; index ++)
    {
        bool quit = false;
        while (!quit)
        {
            unsigned short x = rand()% (WIDTH - 3) + 1;
            unsigned short y = rand()% (HEIGHT - 2) + 1;

            switch (map[y][x])
            {
                case ' ':
                    snakes.push_back( Snake(x,y) );
                    quit = true;
                    break;

                default:
                    x = 0;
                    y = 0;
            }
        }
    }
}


Actually, the default case is redundant, as those values of x and y are replaced by new values next time around.

If there is only the single case to test for, (that is, the selected location is a space), then you could try this:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
void spawnSnakes(char map[HEIGHT][WIDTH], std::vector<Snake> & snakes, unsigned short amount)
{
    for (unsigned short index = 0; index < amount; index ++)
    {       
        unsigned short x, y;

        do {
            x = rand()% (WIDTH  - 3) + 1;
            y = rand()% (HEIGHT - 2) + 1;
        } while (map[y][x] != ' ');

        snakes.push_back( Snake(x,y) );
    }
}

Last edited on
Thank you, that worked well, now I have set up both the spawnSnakes and moveSnakes functions to work like I want them to.
Topic archived. No new replies allowed.