Snake Game (console) - snake display method issue

Started by
8 comments, last by Alberth 7 years, 5 months ago

Hello,

I'm working on a C++ console Snake game. I have the following code:

main.cpp


#include <iostream>
#include "map.h"
#include "snake.h"

using namespace std;

int main()
{
	map Map;
	snake Snake;

	Map.initMap();
	Snake.initSnake();
	Map.updateMap();

	system("pause");

    return 0;
}

map.h


#pragma once

const int row = 20;
const int column = 80;

class map
{
protected:
	char area[row][column];
public:
	void initMap();
	void updateMap();
};

map.cpp


#include "map.h"
#include <iostream>

using namespace std;

void map::initMap()
{
	for (int i = 0; i < row; i++)
	{
		for (int j = 0; j < column; j++)
		{
			if (i == 0 || i == row - 1 || j == 0 || j == column - 1)
			{
				area[i][j] = '#';
			} else {
				area[i][j] = ' ';
			}
		}
	}
}

void map::updateMap()
{
	for (int i = 0; i < row; i++)
	{
		for (int j = 0; j < column; j++)
		{
			cout << area[i][j];
		}
	}
}

snake.h


#pragma once

#include "map.h"
#include <vector>

class snake : public map
{
protected:
	std::vector <char> snakeb;
public:
	void initSnake();
	void updateSnake();
};

snake.cpp


#include "snake.h"
#include <iostream>

using namespace std;

void snake::initSnake()
{
	snakeb.push_back('@');  // head
	snakeb.push_back('o');
	snakeb.push_back('o');

	for (int i = 0; i < snakeb.size(); i++)
	{
		area[row / 2][column / 2 + i] = snakeb[i];  // the snake will spawn in the middle of the map
	}
}


The program compiles fine, but the actual snake body won't get printed in the console. My guess is that the issue is caused because of the inherited snake class.

I would appreciate if I get any piece of advice to better organise (best practice) my code, splitting the different parts of the game into different classes etc.

Thank you! :)

Advertisement

It seems quite odd that your snake is-a map. This does not seem like a correct use of inheritance to me. I would rather you say that a map contains a snake, but that they are otherwise unrelated by inheritance.

Your direct, immediate problem is that you create both a map (called Map) and a snake (called Snake). That means you have two maps: Map and Snake (because snake is-a map).

When you call initSnake on Snake, you fill Snake's area member variable with the graphics for the snake. But you then call Map's updateMap, which draws Map's area to the screen. Map's area is unrelated to Snake's area, and is in fact empty. You never draw the Snake's area by calling Snake.updateMap().

Oh, I think I get it. I modified my main.cpp:


int main()
{
	snake Snake;

	Snake.initMap();
	Snake.initSnake();
	Snake.updateMap();

	system("pause");

    return 0;
}

Now seems to work fine.

However, is this the way you would recommend me to continue the program?

I want to split my program as this:

the map class to contain the methods for:

  • initial map drawing;
  • random fruit spawning on the map;

and the snake class to contain the methods for:

  • the initial snake body;
  • snake movement;
  • snake growing;
  • score update;
  • lose condition.

I would suggest you drop the "using namespace std;" and used "std::" prefix as you do with std::vector.

("using namespace std;" basically pollutes your name space with everything inside "std::", which is a lot. In addition, it becomes harder to understand where you got things from, "std::vector" clearly says "I am coming from std name space", just "vector" might come from it, but maybe not.)

As for your questions, dropping the inheritance from the map sounds like a good idea, as suggested by Josh Petrie. You are also not a planet, even if you live on one.

Instead, let the snake live in the map. The map should know there can be a grid cell filled with a snake piece (just as it knows a grid cell can be filled with fruit).

The snake should track where all its pieces are, a vector of (x,y) coordinates could be useful, for example.

Instead, let the snake live in the map.

You mean to define the initSnake() inside the map class and the rest of the methods concerning the snake inside the snake class?

You mean to define the initSnake() inside the map class and the rest of the methods concerning the snake inside the snake class?

Not that literal "live in the map", better keep snake initialization inside the snake file.

you might want to pass the starting position as parameter though, ie "snake::initSnake(int xstart, int ystart)" or so.

Something like:


snake::initSnake(int pos_x, int pos_y)
{
	snakeb.push_back('@'); //head
	snakeb.push_back('o');
	snakeb.push_back('o');

	for (int i = 0; i < snakeb.size(); i++)
	{
		snakeb[i][0] = pos_x;
		snakeb[i][1] = pos_y;
	}
}

That doesn't seem right.The snakeb is a single dimension array, how do I pass the starting position?

"std::vector <char> snakeb;" says each element of the vector is a "char", ie a single character.

Sure enough, you cannot store a position in a single character.

So instead of a 'char', you need a struct or a class describing the fields in each element, like


struct snakeelement {
    char visual; // Visual displayed for this snake element.
    int xpos;    // X position of the element
    int ypos;    // Y position of the element.
};

In the snake you then make a vector of such elements:


std::vector<snakeelement> snakeb;

// And modify values with
snakeb[i].visual = ..
snakeb[i].xpos = ...

Instead of a "struct" you can use a "class", if you like.

I am not sure "snakeelement" is right, it seems somewhat unreadable to me. I'd use initial caps for type names, like "SnakeElement", but that doesn't fit your "snake" and "map" class names. Perhaps you have a better idea.

One more question: How do I use the push_back function if the typename is a struct?

Thank you!

Hmm, good question. You can often find the answers to such specific question at sites like stack overflow, in this case:

http://stackoverflow.com/questions/13812703/c11-emplace-back-on-vectorstruct

A little story to explain things is probably useful :)

The simple / old solution (before C++11) is


SnakeElement se; // Make new element
se.visual = '@';
se.xpos = 123;
se.ypos = 109; // Fill it
snakeb.push_back(se); // Make a copy of 'se', and put it at the end of the 'snakeb' vector

You make a variable of the struct type, fill it, and then push the filled value at the end of the vector.

This works, but you're making a temporary variable, which is not always wanted.

The vector got fixed in C++11, by adding 'emplace_back'. While push_back can only append existing values (ie it forces you to have a copy of the new element around first), emplace_back can create a new element directly at the new position by using a constructor:


struct SnakeElement {
    // Constructor, initializes a new SnakeElement object.
    SnakeElement(char visual, int xpos, int ypos): visual(visual), xpos(xpos), ypos(ypos)
    {
    }

    char visual;
    int xpos;
    int ypos;
};

std::vector<SnakeElement> elements;
elements.emplace_back('@', 12, 10); // Create new space at the end of the vector, and call the SnakeElement constructor to initialize it.

There are also ways to do this for a struct without having the constructor. In your case it would look like


struct SnakeElement {  // No constructor here.
    char visual;
    int xpos;
    int ypos;
};

std::vector<SnakeElement> elements;
elements.emplace_back( SnakeElement{'@', 12, 10} );

This does the same as above in c++11. The "SnakeElement{'@', 12, 10}" make a new 'SnakeElement' for you, without having a SnakeElement::SnakeElement constructor.

This topic is closed to new replies.

Advertisement