Opinions on best ways to draw a "playing field" (First stage of a simple console game).

Hi there, i usually write too much, so I'll try to summarize everything here and paste my code:

I'm new to C ++, I'm practicing a lot and want to make a simple game on the console, the first step is to "draw" the playing field like this: 1 for the free path, 0 for random obstacles, 2 for the player's location and 7 for the target location. Something like that:

2 1 1 1 1
1 0 0 1 1
1 1 1 1 1
1 1 1 0 1
1 0 1 1 7

I've been reading a lot in this forum and I find it a great place to ask for an opinion on how to improve the code. For example, in this case, it may be better to use arrays than vectors, maybe pointers, iterators in exchange for loops, and so. An informed opinion would be very useful.

Well, this post is already too long compared to the code i'm going to paste.

PS. Sorry for the poor english, I am also working on that.

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
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146

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

using namespace std;

/*
* <!---------- I. DECLARING FUNCTION PROTOTYPES ----------!>
*/
vector<vector<int>> obstacle_generator(int, int);
vector<vector<int>> playfield_generator(int, int, vector<vector<int>>);

int main() {
    /*
    * <!---------- II. DECLARING AND INITIALIZING CONSTANTS ----------!>
    * playfield_size: Size of the 2D vector.
    * total_of_obstacles: Number of obstacles to be printed on the play field.
    * base_number: The play field is built by numbers, the selected base number is 1.
    */
    const int playfield_size = 20;
    const int total_of_obstacles = 10;
    const int base_number = 1;

    /*
    * <!---------- III. CREATE INNER OBSTACLES ----------!>
    * 1. Declare 2D vector that will receive the coordinates of each obstacle on the playing field.
    * 2. Initialize the vector with the content returned by the "obstacle_generator" function, pass the necessary arguments to the function.
    * Note: After declaring and initializing the 2D vector, the calculated coordinates of each obstacle are printed, this is done for informational purposes only, therefore it will be deleted later.
    */

    vector<vector<int>> obstacle_container;
    obstacle_container = obstacle_generator(total_of_obstacles, playfield_size);

    cout << "1. Obstacles list" << endl;
    cout << "-----------------" << endl;
    cout << "These are the coordinates of the obstacle\n(represented by 0) on the playing field." << endl;
    cout << endl;

    for(int iterA = 0; iterA < obstacle_container.size(); iterA++) {
        cout << "[";
        for(int iterB = 0; iterB < obstacle_container[iterA].size(); iterB++) {
            if(iterB > 0) {
                cout << obstacle_container[iterA][iterB];
            } else {
                cout << obstacle_container[iterA][iterB] << ", ";
            }
        }
        if(iterA == (obstacle_container.size() - 1)) {
            cout << "]" << endl;
        } else {
            cout << "]" << ", ";
        }
    }

    /*
    * <!---------- IV. CREATE PLAY FIELD ----------!>
    * 1. Declare the 2D vector that will receive "the layout" of the play field (represented by 1) considering the obstacles (represented by 0) and the default location of the player (represented by 2) and the target (represented by 7).
    * 2. Initialize the vector with the content returned by the "playfield_generator" function, pass the necessary arguments to the function.
    * 3. Print the play field.
    */

    cout << endl;
    cout << "2. Playing field" << endl;
    cout << "----------------" << endl;
    cout << "Available territory (represented by 1)\nfor the player's (2) movement towards his target (7)." << endl;
    cout << endl;

    vector<vector<int>> playfield_container;
    playfield_container = playfield_generator(playfield_size, base_number, obstacle_container);

    //show playfield
    for(int iterX = 0; iterX < playfield_size; iterX++) {
        for(int iterY = 0; iterY < playfield_size; iterY++) {
            cout << playfield_container[iterX][iterY];
        }
        cout << endl;
    }

    return 0;
}

/*
* <!---------- V. FUNCTIONS ----------!>
*/
vector<vector<int>> obstacle_generator(int row_position, int playfield_size) {
    // The constant "col_position" represents the number of columns of the 2D vector,
    // since we want to represent coordinates we initialize it to 2.
    const int col_position = 2;

    vector<vector<int>> obstacle_container(row_position, vector<int>(col_position, 0));
    srand(time(NULL));

    // A loop fills the vector with the random coordinates of the obstacles.
    for(int iterA = 0; iterA < row_position; iterA++) {
        for(int iterB = 0; iterB < col_position; iterB++) {
            obstacle_container[iterA][iterB] = rand() % playfield_size;
        }
    }

    return obstacle_container;
}

vector<vector<int>> playfield_generator(int playfield_size, int base_number, vector<vector<int>> obstacle_container) {
    vector<vector<int>> playfield_container(playfield_size, vector<int>(playfield_size, base_number));

    // A loop fills the vector with the corresponding values.
    // 1 for regular playfield, 0 for obstacles, 2 for player location and 7 for target location.
    for(int iterW = 0; iterW < playfield_size; iterW++) {
        for(int iterX = 0; iterX < playfield_size; iterX++) {


            // This internal loop goes through the vector of obstacles and compares the coordinates of the obstacles
            // with the coordinates of the playing field, when it finds a match it inserts an obstacle (represented by 0).
            for (int iterY = 0; iterY < obstacle_container.size(); iterY++) {
                // The counter variable increases its value every time it finds a match on each coordinate axis,
                // a value of 2 indicates that it found an exact match on each coordinate axis, therefore an obstacle is inserted.
                int counter = 0;
                for (int iterZ = 0; iterZ < obstacle_container[iterY].size(); iterZ++) {
                    if(iterZ == 0) {
                        if(obstacle_container[iterY][iterZ] == iterW) {
                            counter++;
                        }
                    } else {
                        if(obstacle_container[iterY][iterZ] == iterX) {
                            counter++;
                        }
                    }
                    if(counter == 2) {
                        playfield_container[iterW][iterX] = 0;
                    }
                }
            }

            // Assigning the default position of the player and the target on the play field.
            if(iterW == 0 && iterX == 0) {
                playfield_container[iterW][iterX] = 2;
            } else if (iterW == (playfield_size - 1) && iterX == (playfield_size - 1)) {
                playfield_container[iterW][iterX] = 7;
            }
        }
    }

    return playfield_container;
}
At a quick glance this:
1
2
    vector<vector<int>> obstacle_container;
    obstacle_container = obstacle_generator(total_of_obstacles, playfield_size);

Would probably be better as:

 
    vector<vector<int>> obstacle_container = obstacle_generator(total_of_obstacles, playfield_size);


The compiler will probably be better able to use some "tricks" to avoid unnecessary copying.

Next srand() should only be called once, I suggest you move that call to somewhere very early in main().

You may want to consider using ranged based loops for some of your loops. For example:
1
2
3
4
5
6
    // A loop fills the vector with the random coordinates of the obstacles.
    for(int iterA = 0; iterA < row_position; iterA++) {
        for(int iterB = 0; iterB < col_position; iterB++) {
            obstacle_container[iterA][iterB] = rand() % playfield_size;
        }
    }


Could be rewritten as:

1
2
3
4
5
6
7
8
    // A loop fills the vector with the random coordinates of the obstacles.
    for(auto& outer : obstacle_container) 
    {
        for(auto& inner : outer0) 
        {
            inner = rand() % playfield_size;
        }
    }


Lastly for now the playfield_generator() function might benefit from more functions, IMO. It seems to doing too much. That "inner" loop could be placed in a function and then you just call the function instead. The program may also be a candidate for a class or so.




Last edited on
2-d constructs can be trouble or beautiful.
Pros: they stretch in both directions as a vector built, they are easy to index in human readable format, easy to understand and work with.
cons: performance, across the board, can get nasty with big ones. Every time you grab one, you call number of rows constructors and destructors for the internal vectors. Each of those is a new/delete pair at least once, and possibly many times if the vectors grow to fit some unknown size. Also, copying is more complex, it has to copy each internal vector one by one. Also, it fragments the memory across the rows; each row can be in a different page of memory and cause massive page faulting. Those are just a few examples of how they bite you. Consider 1-d if any of this concerns you. As small as this is, it will not matter; but if you use the same ideas in a big program, it will.

it may be better to use arrays than vectors, maybe pointers, iterators in exchange for loops, and so. An informed opinion would be very useful.

the small speed gain of arrays is not usually worth the hassle. I use arrays because I learned the language before vector existed and often, it is true, I do not need the capability of vectors. But this is just me being old and set in my ways. Use vectors. Pointers: use pointers as much as you like. But do not use dynamic memory directly at all if you can possibly avoid it. In general, use a reference where a reference will do, and a pointer if it will not, and a container instead of dynamic memory unless you have a really hard core argument against it (like, you insist on having a classical pointer based tree construct). Iterators are used because you use stl containers :) I find them awkward, but you can't really live without them either, so get used to it and use the darn things.

Iterators do not replace loops, they honestly are closest to an abstraction of a pointer, consider:
int x[100];
int *ip = x;
for(; ip<ip+100; ip++) *ip= rand();
that ^^^ looks an awful lot like an iterator, right? But you still have to loop. Calling <algorithms> that eat iterators for parameters is still a loop, its just that the loop is in code you did not write now. Hiding a loop does not eliminate it. You should use <algorithms> and iterators etc; I am not saying to not use them just trying to show that you can't replace a loop if you want something looped -- somewhere, somehow, something has to cycle whether its recursion, unrolled, in a library, or whatever.
Last edited on
Pointers: use pointers as much as you like. But do not use dynamic memory directly at all if you can possibly avoid it.

Smart pointers. std::unique_ptr since C++11 can manage a dynamically-allocated array of objects, with a member operator[] for indexed access to the managed array.
i like doing tricky stuff like this bc i got sick of output constantly scrolling via resetting the cursor position. Clearing the console to get rid of text that no longer is needed etc. You can also get fancy and change the background and character colors etc if you want to get real fancy.

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
	ShowWindow(FindWindow("ConsoleWindowClass", NULL), 3);  //maximize window
	HANDLE console = GetStdHandle(STD_OUTPUT_HANDLE); //get console handle

	CONSOLE_SCREEN_BUFFER_INFO csbi;
	GetConsoleScreenBufferInfo(console, &csbi);

	const COORD ogCursorPos{ csbi.dwCursorPosition.X, csbi.dwCursorPosition.Y };
	
	while(1) {  //if this was going to output repeatedly reset cursor everytime
		SetConsoleCursorPosition(console, ogCursorPos);
		for (int iterX = 0; iterX < playfield_size; iterX++) {
			for (int iterY = 0; iterY < playfield_size; iterY++) {
				cout << playfield_container[iterX][iterY];
			}
			cout << endl;
		}
	}

void clearPlayField(COORD ogCursor, HANDLE console) { 

	CONSOLE_CURSOR_INFO ci;
	ci.dwSize = 100;
	ci.bVisible = false;
	SetConsoleCursorInfo(console, &ci);
	SetConsoleCursorPosition(console, ogCursor);

	for (int iterX = 0; iterX < playfield_size; iterX++) {
		for (int iterY = 0; iterY < playfield_size; iterY++) {
			cout << " ";
		}
		cout << endl;
	}

	ci.dwSize = 25;
	ci.bVisible = true;
	SetConsoleCursorInfo(console, &ci); // reset cursor to visible 

	// or clear the whole window

	CONSOLE_SCREEN_BUFFER_INFO csbi;
	GetConsoleScreenBufferInfo(console, &csbi);
	COORD coord{ 0,0 };
	DWORD written{};

	FillConsoleOutputCharacter(console, ' ', csbi.dwSize.Y * csbi.dwSize.X, coord, &written);
}
Last edited on
Topic archived. No new replies allowed.