@Harbon

На сколько корректна такая реализация?

Я пишу игру змейка на C++ с использованием SFML библиотеки. Возникла проблема. Мне нужно в классе GameField использовать методы класса Snake, а в классе Snake использовать методы GameField. Если просто создать прототип класса GameField перед классом Snake, то я могу только ссылаться на класс GameField, но использовать методы класса не могу.
Подскажите, корректно ли будет создать прототип класса GameField перед классом Snake, и реализацию метода класса вынести после класса GameField. Не считается, что это костыль? Есть ли другие пути решения данной проблемы?
#include <SFML/Graphics.hpp>
#include <iostream>
#include <string>

class GameField;

class Snake
{
private:
	sf::Vector2i snakeVector;
	std::string pathToSnakeImage;
	sf::Image snakeImage;
	sf::Texture snakeTexture;
	sf::Sprite snakeSprite;
public:
	Snake(sf::Vector2i snakeVector, std::string pathToSnakeImage)
	{
		this->snakeVector = snakeVector;
		this->pathToSnakeImage = pathToSnakeImage;
		snakeImage.loadFromFile(this->pathToSnakeImage);
		snakeTexture.loadFromImage(snakeImage);
		snakeSprite.setTexture(snakeTexture);
	}
	sf::Sprite getSnakeSprite(void)
	{
		return snakeSprite;
	}
	void setSnakePosition(sf::Vector2f snakeVector)
	{
		snakeSprite.setPosition(snakeVector);
	}
	void snakeUpdate(GameField &gameField);
};

class GameField
{
private:
	int** gameField;
	int heightGameField;
	int widthGameField;
	std::string pathToGameField;
	sf::Image gameFieldImage;
	sf::Texture gameFieldTexture;
	sf::Sprite gameFieldSprite;
	int sizeGameFieldSprite = 32;
public:
	GameField(int heightGameField, int widthGameField, std::string pathToGameFieldBorder)
	{
		this->heightGameField = heightGameField;
		this->widthGameField = widthGameField;
		this->pathToGameField = pathToGameFieldBorder;
		gameFieldImage.loadFromFile(this->pathToGameField);
		gameFieldTexture.loadFromImage(gameFieldImage);
		gameFieldSprite.setTexture(gameFieldTexture);
		gameField = new int*[heightGameField];
		for (int i = 0; i < heightGameField; i++)
			gameField[i] = new int[widthGameField];
	}
	void clear()
	{
		for (int i = 0; i < heightGameField; i++)
			for (int j = 0; j < widthGameField; j++)
				gameField[i][j] = 0;
	}
	int** getGameField(void)
	{
		return gameField;
	}
	void drawGameField(sf::RenderWindow &window, Snake &snake)
	{
		for (int i = 0; i < heightGameField; i++)
		{
			for (int j = 0; j < widthGameField; j++)
			{
				switch (gameField[i][j])
				{
				case 0:
					window.draw(gameFieldSprite);
					gameFieldSprite.setPosition(sf::Vector2f(sizeGameFieldSprite * i, sizeGameFieldSprite * j));
					break;
				case 1:
					window.draw(snake.getSnakeSprite());
					snake.setSnakePosition(sf::Vector2f(sizeGameFieldSprite * i, sizeGameFieldSprite * j));
					break;
				}
			}
		}
	}
	~GameField()
	{
		for (int i = 0; i < heightGameField; i++)
			delete[] gameField[i];
		delete[] gameField;
	}
};

void Snake::snakeUpdate(GameField& gameField)
{
	gameField.getGameField()[5][5] = 1;
}

int main()
{
	GameField gameField(35, 25, "C:/Users/White/Documents/VSProgects/SFML/x64/Release/Image/GameField.png");
	gameField.clear();
	Snake snake(sf::Vector2i(17, 12),"C:/Users/White/Documents/VSProgects/SFML/x64/Release/Image/Snake.png");
	sf::RenderWindow window(sf::VideoMode(35*32, 25*32), "Snake", sf::Style::Close);
	while (window.isOpen())
	{
		sf::Event event;
		while (window.pollEvent(event))
		{
			if (event.type == sf::Event::Closed)
			{
				window.close();
			}
		}
		window.clear();
		snake.snakeUpdate(gameField);
		gameField.drawGameField(window, snake);
		window.display();
	}
	return 0;
}
  • Вопрос задан
  • 117 просмотров
Решения вопроса 1
wataru
@wataru Куратор тега C++
Разработчик на С++, экс-олимпиадник.
Во-первых, стоит эти два класса разнести по разным файлам (декларация в .h файле, реализация в .cpp).

А дальше надо поменять иерархию ваших классов.
С такими циклическими зависимостями, конечно, можно что-то даже скомпилировать (в каждом cpp включать другой хедер, в интерфейсе второй класс передавать только как указатель. Или у вас в одном файле сначала сделать оба прототипа, forward declaration второго класса перед ними, потом реализации). Но это будет запутанно и сложно.

Подумайте, а так ли вам надо в snakeUpdate передавать GameField? Можно, например, из Snake возвращать ее координаты или что там влияет на Update и потом их передавать какому-то методу в GameField. Сейчас ваш snakeUpdate вообще ничего из Snake не задействует - его можно наружу вывести, но это, наверно, еще в разработке. Не надо без крайней необходимости создавать усложненную структуру зависимостей классов. Решите, какой класс будет более важным, и пусть не ему передается второй класс, а второй класс наоборот дергает какие-то его методы.
Ответ написан
Пригласить эксперта
Ответы на вопрос 1
mayton2019
@mayton2019
Bigdata Engineer
Тут зубчатый массив не нужен. Можно и одномерным прекрасно обойтись.
for (int i = 0; i < heightGameField; i++)
      gameField[i] = new int[widthGameField];


Тут всё поле можно не перерисовывать. Змейка это вообще - эконом-игра. В ней можно обновлять
только голову змеи и хвост. И те места на карте где выпадает еда.

for (int i = 0; i < heightGameField; i++)
    {
      for (int j = 0; j < widthGameField; j++)
      {
        switch (gameField[i][j])

Я не кодил на SFML/Graphics.hpp. Моя С++ библиотека когда-то называлась Borland C++ BGI. Но там мне хватало
вот такого частичного обновления экрана.

Зачем этот метод? Он безсмысленный.
int** getGameField(void)
  {
    return gameField;
  }

Он взламывает инкапсуляцию класса. После него всё ООП должно пойди по звезде. Вобщем можешь его даже не делать.

Вообще в данной игровой логике ООП не очень нужно. Ну тоесть я не вижу ниакой мотивации к ООП. Между змеей и локацией нет никакой секретности. Короче пиши набор функций и не парься. Если SFML жостко не требует ООП
то и не беспокойся об этом. Шаблонизация тут как я понимаю важнее чем ООП. А об этом еще великий Степанов
говорил.
Ответ написан
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы