Help with my game program architecture

Started by
1 comment, last by Sky Warden 8 years, 2 months ago

Hello everyone I'm quite new to programming and game programming in particular. My currecnt challenge project is to make a tic-tac-toe game. I use C++ combined with SFML and I'm having some difficulties designing the structure of my game.

Tutorials on SFML read that there should be a statement called gameloop in which there are three function called respectively processInput(), update() and draw(). The problem is that I can't decide which lines of code should be placed in each of those functions.

My game class looks like this:


class TicTacToeGame
{
	enum GameState { MAIN_MENU, PLAYING, PLAYER_TURN, COMPUTER_TURN };
	enum Mark { SPACE, CROSS, NOUGHT, };

	GameState currentState;

	Mark state[3][3];

	struct Cell
	{
		Vector2f pos;
		Mark mark;
	};

	RenderWindow window;

	Mark playerMark;
	Mark computerMark;

	Sprite fieldSprite;
	Sprite frameSprite;

	Texture fieldTexture;
	Texture noughtTexture;
	Texture crossTexture;
	Texture greenFrameTexture;
	Texture redFrameTexture;

	Font font;
	Text message;

	int row;
	int col;

	std::vector<Sprite> sprites;

	bool started;

	void update();
	void processInput(Keyboard::Key key);
	void handleEvents();
	void draw();
	void putMark(const int row, const int col, Mark mark);
	void playerTurn();
	void startGame();
public:
	TicTacToeGame();
	void gameLoop();
};

The first thing is that if I follow the way how games are made with SFML in tutorials I face my update method being devoid of instructions that are actually updating the game logic, my humble game featuring no animation, no objects moving on the screen, no music etc.

Another problem is that my proccesInput() method includes statements that, as I find it, should be placed in update(), though I don't know how to do it in a proper way.

Then, I'm not certain about handling game screens (game states) making games with SFML. I declared an enumeration type for representation each of the states, but it's pretty clear to me that it's not sufficient since in every game state there is a set of objects (for example a text of welcome on the initial game screen, some background etc) that are created on switching to a state and destroyed on switching off to another. I of course could re-instantiate those objects over and over again while the game cycles through processInput(), update() and draw() just by declaring those objects in the methods, but I think it's too sily and ugly way of handling things like this. Moreover, in this case I have to set the origin, position, etc of the objects. For example, if I want a text to be displayed on the screen once the player loses the game, I'd have to declare a text variable, set its font, character size, string, origin, position right in the draw() method, which again seems not a very good practice. In addtition, there are some variables that must be re-assigned when the player causes the game come out of one state and get into another.

Below there are the metods that I've implemented so far:


void TicTacToeGame::processInput(Keyboard::Key key)
{
	if (currentState == PLAYER_TURN) {

		if (!started) {

			row = 0; 
			col = 0;
		}
		


		if (key == Keyboard::Up) {

			if (row > 0) {

				row--;
				frameSprite.setPosition(frameSprite.getPosition().x, frameSprite.getPosition().y - 150.0);
			}


		}
		else if (key == Keyboard::Down) {

			if (row < 2) {

				row++;
				frameSprite.setPosition(frameSprite.getPosition().x, frameSprite.getPosition().y + 150.0);
			}
		}
		else if (key == Keyboard::Left) {

			if (col > 0) {

				col--;
				frameSprite.setPosition(frameSprite.getPosition().x - 150.0, frameSprite.getPosition().y);
			}
		}
		else if (key == Keyboard::Right) {

			if (col < 2) {

				col++;
				frameSprite.setPosition(frameSprite.getPosition().x + 150.0, frameSprite.getPosition().y);
			}
		}

		if (state[row][col] == SPACE) {

			frameSprite.setTexture(greenFrameTexture);
		}
		else {

			frameSprite.setTexture(redFrameTexture);
		}

		if (key == Keyboard::Space) {

			putMark(row, col, playerMark);

		}
	}
}

the handleEvents method:


void TicTacToeGame::handleEvents()
{
	sf::Event event;
	while (window.pollEvent(event))
	{

		if (event.type == Event::KeyPressed) {

			processInput(event.key.code);
		}
		else if (event.type == Event::Closed) {

			window.close();
		}
		
	}
}

the initializer:


TicTacToeGame::TicTacToeGame() : window(VideoMode(800, 600), "ticTacToeGame")
{
	currentState = PLAYER_TURN;

	started = false;

	fieldTexture.loadFromFile("assets/field.png");
	noughtTexture.loadFromFile("assets/nought.png");
	crossTexture.loadFromFile("assets/cross.png");
	greenFrameTexture.loadFromFile("assets/frame_green.png");
	redFrameTexture.loadFromFile("assets/frame_red.png");


	fieldSprite.setTexture(fieldTexture);
	fieldSprite.setScale(0.5, 0.5);
	fieldSprite.setOrigin(fieldSprite.getLocalBounds().left + fieldSprite.getLocalBounds().width / 2.0,
		fieldSprite.getLocalBounds().top + fieldSprite.getLocalBounds().height / 2.0);
	//fieldSprite.setPosition(window.getSize().x / 2.0, window.getSize().y / 2.0);


	frameSprite.setTexture(greenFrameTexture);
	frameSprite.setScale(0.5, 0.5);
	frameSprite.setOrigin(frameSprite.getLocalBounds().left + frameSprite.getLocalBounds().width / 2.0,
		frameSprite.getLocalBounds().top + frameSprite.getLocalBounds().height / 2.0);

	/*for (int i = 0; i < 2; i++)
		for (int j = 0; j < 3; j++)
			putMark(i, j, NOUGHT);
			*/
	playerMark = NOUGHT;

	if (!font.loadFromFile("arial.ttf")) {

		std::cout << "Unable to load font" << std::endl;
		return;
	}

	message.setString("test");
	message.setFont(font);
	message.setColor(Color::Black);
	message.setCharacterSize(32);
	message.setPosition(fieldSprite.getGlobalBounds().left, fieldSprite.getGlobalBounds().top + fieldSprite.getGlobalBounds().height);
}

the draw() method:


void TicTacToeGame::draw()
{

	window.draw(fieldSprite);

	for (int i = 0; i < sprites.size(); i++)
		window.draw(sprites[i]);

	window.draw(frameSprite);

	window.draw(message);

}

the putMark method that accepts row and column on an invisible two dimensional grid, and in case they are correct changes an element of the array and then places a mark on the screen:


void TicTacToeGame::putMark(const int row, const int col, Mark mark)
{
	if (row < 0 || row > 2 || col < 0 || col > 2)
		return;

	if (state[row][col] != SPACE)
		return;

	state[row][col] = mark;

	Sprite sprite;

	if (mark == NOUGHT) {

		sprite.setTexture(noughtTexture);
	}
	else if (mark == CROSS) {

		sprite.setTexture(crossTexture);
	}	
	else {

		return;
	}

	FloatRect rect = sprite.getLocalBounds();
	sprite.setOrigin(rect.left + rect.width / 2.0, rect.top + rect.height / 2.0);
	sprite.setScale(0.5, 0.5);


	if (row == 0 && col == 0) {

		sprite.setPosition(fieldSprite.getPosition().x - 150.0, fieldSprite.getPosition().y - 150.0);
	}
	else if (row == 0 && col == 1) {

		sprite.setPosition(fieldSprite.getPosition().x, fieldSprite.getPosition().y - 150.0);
	}
	else if (row == 0 && col == 2) {

		sprite.setPosition(fieldSprite.getPosition().x + 150.0, fieldSprite.getPosition().y - 150.0);
	}
	else if (row == 1 && col == 0) {

		sprite.setPosition(fieldSprite.getPosition().x - 150.0, fieldSprite.getPosition().y);
	}
	else if (row == 1 && col == 1) {

		sprite.setPosition(fieldSprite.getPosition().x, fieldSprite.getPosition().y);
	}
	else if (row == 1 && col == 2) {

		sprite.setPosition(fieldSprite.getPosition().x + 150.0, fieldSprite.getPosition().y);
	}
	else if (row == 2 && col == 0) {

		sprite.setPosition(fieldSprite.getPosition().x - 150.0, fieldSprite.getPosition().y + 150.0);
	}
	else if (row == 2 && col == 1) {

		sprite.setPosition(fieldSprite.getPosition().x, fieldSprite.getPosition().y + 150.0);
	}
	else if (row == 2 && col == 2) {

		sprite.setPosition(fieldSprite.getPosition().x + 150.0, fieldSprite.getPosition().y + 150.0);
	}

	sprites.push_back(sprite);

	return;

}

Sorry for posting too much crappy code here.

I've skimmed through simple games written in other programming languages and/or made with the help of another frameworks and instruments, and they (their codes) look totaly different from how they might have been possibly made with C++ and SFML and simply beyond my comprehension.

Advertisement

Perhaps trying an entity component system would help?

https://en.wikipedia.org/wiki/Entity_component_system

They call me the Tutorial Doctor.


Then, I'm not certain about handling game screens (game states) making games with SFML. I declared an enumeration type for representation each of the states, but it's pretty clear to me that it's not sufficient since in every game state there is a set of objects (for example a text of welcome on the initial game screen, some background etc) that are created on switching to a state and destroyed on switching off to another. I of course could re-instantiate those objects over and over again while the game cycles through processInput(), update() and draw() just by declaring those objects in the methods, but I think it's too sily and ugly way of handling things like this. Moreover, in this case I have to set the origin, position, etc of the objects. For example, if I want a text to be displayed on the screen once the player loses the game, I'd have to declare a text variable, set its font, character size, string, origin, position right in the draw() method, which again seems not a very good practice. In addtition, there are some variables that must be re-assigned when the player causes the game come out of one state and get into another.

You can make a base class for state and child classes of it for each specific state, and create and destroy its objects in its constructor and destructor. Then you can make a state handler class that contains those states, in a container of type pointer of the state base class, and handles the switching.

You can even define specific processInput(), update() and draw() methods for each state so they can have different behavior. For example, a menu state only draws play and exit buttons while the game state draws the board and handles the game commands. On the state handler class you can set these methods too, to command the active state to call its respective methods.

Also, if you need to share resources among states, like textures, fonts, etc, you can declare them in the state handler class and share them to the states.

This topic is closed to new replies.

Advertisement