This is my second attempt at creating a game. I think I have gotten the basic ideas of state machines and game loops down, but I'm sure there are still a lot of things I can learn.
It is a basic Tic-Tac-Toe game. There is a menu where you can choose to play vs AI or another player. If the player is vs AI, there is a 50% chance the AI will have the first turn. Players take turn placing marks trying to get 3 in a row.
Once the game has ended, the board screen fades slightly so you can still see the board and a play again menu pops up.
Main.cpp
#include <iostream>
#include <vector>
#include <random>
#include <SDL.h>
#include <SDL_image.h>
#include <SDL_ttf.h>
#include "SDLInit.h"
#include "Texture.h"
constexpr int SCREEN_WIDTH = 640;
constexpr int SCREEN_HEIGHT = 480;
enum class GameState
{
MAIN_MENU,
GAME_LOOP,
PLAY_AGAIN,
EXIT,
};
//used to determine where to draw strikethough after win
enum class Line
{
NONE,
ROW_ONE,
ROW_TWO,
ROW_THREE,
COL_ONE,
COL_TWO,
COL_THREE,
DIAG_ONE,
DIAG_TWO,
};
struct Game
{
//2d vector to represent board
Game()
{
board.resize(3, std::vector<int>(3));
}
void resetBoard();
bool hasWon();
bool isDraw();
void placeMark(int row, int col);
void placeMarkAI();
std::vector<std::vector <int> > board;
bool playerOneTurn_ = true;
bool gameWasWon_ = false;
bool gameWasDraw_ = false;
bool vsAI_ = false;
Line winningLine_ = Line::NONE;
};
struct AssetManager
{
//unable to initialize a vector of textures in class
void loadButtonTextures();
void setBlendMode(SDL_BlendMode blendmode);
void drawStrikeThrough(Game& game);
std::unique_ptr<SDL_Window, sdl_deleter> window_{ SDL_CreateWindow("TicTacToe", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, SCREEN_WIDTH, SCREEN_HEIGHT, SDL_WINDOW_SHOWN) };
std::unique_ptr<SDL_Renderer, sdl_deleter> renderer_{ SDL_CreateRenderer(window_.get(), -1, SDL_RENDERER_PRESENTVSYNC) };
std::unique_ptr<TTF_Font, sdl_deleter> font_{ TTF_OpenFont("resources/opensans.ttf", 32) };
SDL_Color fontColor_ = { 0xff, 0xff, 0xff };
int fontWrapWidth_ = SCREEN_WIDTH;
LTexture menuTexture_{ from_surface, renderer_.get(), "resources/menu.png" };
LTexture onePlayerButton_{ from_surface, renderer_.get(), "resources/oneplayer.png" };
LTexture twoPlayerButton_{ from_surface, renderer_.get(), "resources/twoplayer.png" };
LTexture boardTexture_{ from_surface, renderer_.get(), "resources/board.png" };
LTexture yesButton_{ from_surface, renderer_.get(), "resources/yes.png" };
LTexture noButton_{ from_surface, renderer_.get(), "resources/no.png" };
LTexture strikeThrough_{ from_surface, renderer_.get(), "resources/strikethrough.png" };
LTexture gridButton_[3][3];
LTexture playerOneWinsText_{ from_text, font_.get(), renderer_.get(), "Player One Wins!", fontColor_, fontWrapWidth_ };
LTexture playerTwoWinsText_{ from_text, font_.get(), renderer_.get(), "Player Two Wins!", fontColor_, fontWrapWidth_ };
LTexture gameWasDrawText_{ from_text, font_.get(), renderer_.get(), "It's a Draw!", fontColor_, fontWrapWidth_ };
LTexture playAgainText_{ from_text, font_.get(), renderer_.get(), "Play Again?", fontColor_, fontWrapWidth_ };
//'blank', 'x', or 'o'
SDL_Rect spriteClips[3] = { { 0,0, 100, 100 },{ 100, 0, 100, 100 },{ 200, 0, 100, 100 } };
};
std::mt19937& random_engine()
{
static std::mt19937 mersenne(std::random_device{}());
return mersenne;
}
int getRandomNumber(int x, int y)
{
std::uniform_int_distribution<> dist{ x,y };
return dist(random_engine());
}
bool button_is_pressed(SDL_Event const& event, SDL_Rect const& button_rect)
{
if (event.type == SDL_MOUSEBUTTONDOWN)
{
auto const& mouse_button_event = event.button;
auto const mouse_position = SDL_Point{ mouse_button_event.x, mouse_button_event.y };
return (mouse_button_event.button == SDL_BUTTON_LEFT) && SDL_PointInRect(&mouse_position, &button_rect);
}
return false;
}
void Game::resetBoard()
{
//clear board
for (int row = 0; row < 3; row++)
{
for (int col = 0; col < 3; col++)
{
board[row][col] = 0;
}
}
//reset all members
playerOneTurn_ = true;
gameWasWon_ = false;
gameWasDraw_ = false;
vsAI_ = false;
winningLine_ = Line::NONE;
}
bool Game::hasWon()
{
//check rows
for (int row = 0; row < 3; row++)
{
if (board[row][0] != 0 && board[row][0] == board[row][1] && board[row][0] == board[row][2])
{
switch (row)
{
case 0:
winningLine_ = Line::ROW_ONE;
return true;
case 1:
winningLine_ = Line::ROW_TWO;
return true;
case 2:
winningLine_ = Line::ROW_THREE;
return true;
}
}
}
//check cols
for (int col = 0; col < 3; col++)
{
if (board[0][col] != 0 && board[0][col] == board[1][col] && board[0][col] == board[2][col])
{
switch (col)
{
case 0:
winningLine_ = Line::COL_ONE;
return true;
case 1:
winningLine_ = Line::COL_TWO;
return true;
case 2:
winningLine_ = Line::COL_THREE;
return true;
}
}
}
//check diagonal
if (board[0][0] != 0 && board[0][0] == board[1][1] && board[0][0] == board[2][2])
{
winningLine_ = Line::DIAG_ONE;
return true;
}
//check other diagonal
if (board[2][0] != 0 && board[2][0] == board[1][1] && board[2][0] == board[0][2])
{
winningLine_ = Line::DIAG_TWO;
return true;
}
return false;
}
bool Game::isDraw()
{
for (int row = 0; row < 3; row++)
{
for (int col = 0; col < 3; col++)
{
//if there is an empty space
if (board[row][col] == 0)
{
return false;
}
}
}
return true;
}
void Game::placeMark(int row, int col)
{
//if square is empty
if (board[row][col] == 0)
{
//alternate between x's and o's
if (playerOneTurn_)
{
board[row][col] = 1;
playerOneTurn_ = false;
}
else
{
board[row][col] = 2;
playerOneTurn_ = true;
}
}
}
void Game::placeMarkAI()
{
//AI checks for two lines with two 'x's and a blank space
//rows
for (int row = 0; row < 3; row++)
{
if (board[row][0] == 1 && board[row][1] == 1 && board[row][2] == 0)
{
placeMark(row, 2);
return;
}
if (board[row][0] == 1 && board[row][2] == 1 && board[row][1] == 0)
{
placeMark(row, 1);
return;
}
if (board[row][2] == 1 && board[row][1] == 1 && board[row][0] == 0)
{
placeMark(row, 0);
return;
}
}
//columns
for (int col = 0; col < 3; col++)
{
if (board[0][col] == 1 && board[1][col] == 1 && board[2][col] == 0)
{
placeMark(2, col);
return;
}
if (board[0][col] == 1 && board[2][col] == 1 && board[1][col] == 0)
{
placeMark(1, col);
return;
}
if (board[2][col] == 1 && board[1][col] == 1 && board[0][col] == 0)
{
placeMark(0, col);
return;
}
}
//diagonals
if (board[0][0] == 1 && board[1][1] == 1 && board[2][2] == 0)
{
placeMark(2, 2);
return;
}
if (board[0][0] == 1 && board[2][2] == 1 && board[1][1] == 0)
{
placeMark(1, 1);
return;
}
if (board[2][2] == 1 && board[1][1] == 1 && board[0][0] == 0)
{
placeMark(0, 0);
return;
}
if (board[0][2] == 1 && board[1][1] == 1 && board[2][0] == 0)
{
placeMark(2, 0);
return;
}
if (board[2][0] == 1 && board[1][1] == 1 && board[0][2] == 0)
{
placeMark(0, 2);
return;
}
if (board[2][0] == 1 && board[0][2] == 1 && board[1][1] == 0)
{
placeMark(1, 1);
return;
}
//else mark random empty spot
int row = getRandomNumber(0, 2);
int col = getRandomNumber(0, 2);
while (board[row][col] != 0)
{
row = getRandomNumber(0, 2);
col = getRandomNumber(0, 2);
}
board[row][col] = 2;
}
void AssetManager::drawStrikeThrough(Game& game)
{
switch (game.winningLine_)
{
case Line::ROW_ONE:
strikeThrough_.render(renderer_.get(), SCREEN_WIDTH / 2 - strikeThrough_.mWidth / 2 , SCREEN_HEIGHT / 2 - strikeThrough_.mHeight / 2 - 120);
break;
case Line::ROW_TWO:
strikeThrough_.render(renderer_.get(), SCREEN_WIDTH / 2 - strikeThrough_.mWidth / 2, SCREEN_HEIGHT / 2 - strikeThrough_.mHeight / 2);
break;
case Line::ROW_THREE:
strikeThrough_.render(renderer_.get(), SCREEN_WIDTH / 2 - strikeThrough_.mWidth / 2, SCREEN_HEIGHT / 2 - strikeThrough_.mHeight / 2 + 120);
break;
case Line::COL_ONE:
strikeThrough_.render(renderer_.get(), SCREEN_WIDTH / 2 - strikeThrough_.mWidth / 2 - 120, SCREEN_HEIGHT / 2 - strikeThrough_.mHeight / 2, nullptr, 90);
break;
case Line::COL_TWO:
strikeThrough_.render(renderer_.get(), SCREEN_WIDTH / 2 - strikeThrough_.mWidth / 2, SCREEN_HEIGHT / 2 - strikeThrough_.mHeight / 2, nullptr, 90);
break;
case Line::COL_THREE:
strikeThrough_.render(renderer_.get(), SCREEN_WIDTH / 2 - strikeThrough_.mWidth / 2 + 120, SCREEN_HEIGHT / 2 - strikeThrough_.mHeight / 2, nullptr, 90);
break;
case Line::DIAG_ONE:
strikeThrough_.render(renderer_.get(), SCREEN_WIDTH / 2 - strikeThrough_.mWidth / 2, SCREEN_HEIGHT / 2 - strikeThrough_.mHeight / 2, nullptr, 45);
break;
case Line::DIAG_TWO:
strikeThrough_.render(renderer_.get(), SCREEN_WIDTH / 2 - strikeThrough_.mWidth / 2, SCREEN_HEIGHT / 2 - strikeThrough_.mHeight / 2, nullptr, 135);
break;
}
}
bool playAgain_render(Game& game, AssetManager& assets)
{
if (game.gameWasWon_)
{
//if player's one turn, it means player two was last to make a move and vice versa
if (!game.playerOneTurn_)
{
assets.playerOneWinsText_.render(assets.renderer_.get(), SCREEN_WIDTH/2 - assets.playerOneWinsText_.mWidth/2, 25);
}
else
{
assets.playerTwoWinsText_.render(assets.renderer_.get(), SCREEN_WIDTH / 2 - assets.playerTwoWinsText_.mWidth / 2, 25);
}
}
else
{
assets.gameWasDrawText_.render(assets.renderer_.get(), SCREEN_WIDTH / 2 - assets.gameWasDrawText_.mWidth / 2, 25);
}
assets.playAgainText_.render(assets.renderer_.get(), SCREEN_WIDTH / 2 - assets.playAgainText_.mWidth / 2, SCREEN_HEIGHT - 100);
assets.yesButton_.render(assets.renderer_.get(), SCREEN_WIDTH / 2 - assets.yesButton_.mWidth, SCREEN_HEIGHT - assets.yesButton_.mHeight - 10);
assets.noButton_.render(assets.renderer_.get(), SCREEN_WIDTH / 2, SCREEN_HEIGHT - assets.yesButton_.mHeight - 10);
SDL_RenderPresent(assets.renderer_.get());
return true;
}
bool playAgain_update()
{
return true;
}
bool playAgain_input(GameState& gameState, Game& game, AssetManager& assets)
{
SDL_Event e;
while (SDL_PollEvent(&e) != 0)
{
if (e.type == SDL_QUIT)
{
gameState = GameState::EXIT;
return false;
}
if (button_is_pressed(e, assets.yesButton_.mButton))
{
gameState = GameState::MAIN_MENU;
game.resetBoard();
return false;
}
if (button_is_pressed(e, assets.noButton_.mButton))
{
gameState = GameState::EXIT;
return false;
}
}
return true;
}
bool gameLoop_render(Game& game, AssetManager& assets)
{
SDL_SetRenderDrawColor(assets.renderer_.get(), 0x21, 0xb3, 0xfa, 0xFF);
SDL_RenderClear(assets.renderer_.get());
assets.boardTexture_.render(assets.renderer_.get(), SCREEN_WIDTH / 2 - assets.boardTexture_.mWidth / 2, SCREEN_HEIGHT / 2 - assets.boardTexture_.mHeight / 2);
for (int row = 0; row < 3; row++)
{
for (int col = 0; col < 3; col++)
{
assets.gridButton_[row][col].render(assets.renderer_.get(), (SCREEN_WIDTH / 2 - assets.boardTexture_.mWidth / 2) + col * 120, (SCREEN_HEIGHT / 2 - assets.boardTexture_.mHeight / 2) + row * 120, &assets.spriteClips[game.board[row][col]]);
}
}
if (game.winningLine_ != Line::NONE)
{
assets.drawStrikeThrough(game);
}
SDL_RenderPresent(assets.renderer_.get());
return true;
}
bool gameLoop_update(GameState& gameState, Game& game, AssetManager& assets)
{
if (game.hasWon())
{
//without this, game will skip to play again screen without showing the last move
gameLoop_render(game, assets);
game.gameWasWon_ = true;
gameState = GameState::PLAY_AGAIN;
return false;
}
if (game.isDraw())
{
//without this, game will skip to play again screen without showing the last move
gameLoop_render(game, assets);
game.gameWasDraw_ = true;
gameState = GameState::PLAY_AGAIN;
return false;
}
if (game.vsAI_ && game.playerOneTurn_ == false)
{
game.placeMarkAI();
game.playerOneTurn_ = true;
}
return true;
}
bool gameLoop_input(GameState& gameState, Game& game, AssetManager& assets)
{
SDL_Event e;
while (SDL_PollEvent(&e) != 0)
{
if (e.type == SDL_QUIT)
{
gameState = GameState::EXIT;
return false;
}
for (int row = 0; row < 3; row++)
{
for (int col = 0; col < 3; col++)
{
if (button_is_pressed(e, assets.gridButton_[row][col].mButton))
{
game.placeMark(row, col);
return true;
}
}
}
}
return true;
}
bool mainMenu_render(AssetManager& assets)
{
SDL_RenderClear(assets.renderer_.get());
assets.menuTexture_.render(assets.renderer_.get(), 0, 0);
assets.onePlayerButton_.render(assets.renderer_.get(), SCREEN_WIDTH / 2 - assets.onePlayerButton_.mWidth / 2, SCREEN_HEIGHT/2);
assets.twoPlayerButton_.render(assets.renderer_.get(), SCREEN_WIDTH / 2 - assets.twoPlayerButton_.mWidth / 2, SCREEN_HEIGHT/2 + assets.onePlayerButton_.mHeight + 25);
SDL_RenderPresent(assets.renderer_.get());
return true;
}
bool mainMenu_update()
{
return true;
}
bool mainMenu_input(GameState& gameState, Game& game, AssetManager& assets)
{
SDL_Event e;
while (SDL_PollEvent(&e) != 0)
{
if (e.type == SDL_QUIT)
{
gameState = GameState::EXIT;
return false;
}
if (button_is_pressed(e, assets.onePlayerButton_.mButton))
{
gameState = GameState::GAME_LOOP;
game.vsAI_ = true;
//randomly choose who goes first
int whoGoesFirst = getRandomNumber(0, 1);
if (whoGoesFirst == 1)
{
game.playerOneTurn_ = false;
}
return false;
}
if (button_is_pressed(e, assets.twoPlayerButton_.mButton))
{
gameState = GameState::GAME_LOOP;
return false;
}
}
return true;
}
void AssetManager::loadButtonTextures()
{
//unable to initialize vector directly in class
for (int row = 0; row < 3; row++)
{
for (int col = 0; col < 3; col++)
{
gridButton_[row][col].loadfromsurface(renderer_.get(), "resources/xospritesheet.png");
}
}
}
//used to allow fade affect after game over
void AssetManager::setBlendMode(SDL_BlendMode blendmode)
{
SDL_SetRenderDrawBlendMode(renderer_.get(), blendmode);
}
int main(int argc, char* args[])
{
//initialize sdl systems
sdl sdlinit;
sdl_img img;
sdl_ttf ttf;
GameState gameState = GameState::MAIN_MENU;
Game game;
AssetManager assets;
assets.loadButtonTextures();
//used to fade game grid after game over
assets.setBlendMode(SDL_BLENDMODE_BLEND);
while (gameState != GameState::EXIT)
{
switch (gameState)
{
case GameState::MAIN_MENU:
{
while (mainMenu_input(gameState, game, assets) &&
mainMenu_update() &&
mainMenu_render(assets))
{}
break;
}
case GameState::GAME_LOOP:
{
while (gameLoop_input(gameState, game, assets) &&
gameLoop_update(gameState, game, assets) &&
gameLoop_render( game, assets))
{}
break;
}
case GameState::PLAY_AGAIN:
{
//fade screen slightly
SDL_Rect fade = { 0,0, SCREEN_WIDTH, SCREEN_HEIGHT };
SDL_SetRenderDrawColor(assets.renderer_.get(), 0, 0, 0, 0x2b);
SDL_RenderFillRect(assets.renderer_.get(), &fade);
SDL_RenderPresent(assets.renderer_.get());
while (playAgain_input(gameState, game, assets) &&
playAgain_update() &&
playAgain_render(game, assets))
{}
break;
}
}
}
return 0;
}
SDLInit.h
#pragma once
class sdl
{
public:
sdl();
sdl(sdl&& other) noexcept;
~sdl();
auto operator=(sdl&& other) noexcept->sdl&;
// Non-copyable
sdl(sdl const&) = delete;
auto operator=(sdl const&)->sdl& = delete;
friend void _swap(sdl& a, sdl& b) noexcept;
private:
bool _own = false;
};
class sdl_img
{
public:
sdl_img();
sdl_img(sdl_img&& other) noexcept;
~sdl_img();
auto operator=(sdl_img&& other) noexcept->sdl_img&;
// Non-copyable
sdl_img(sdl_img const&) = delete;
auto operator=(sdl_img const&)->sdl_img& = delete;
friend void _swap(sdl_img& a, sdl_img& b) noexcept;
private:
bool _own = false;
};
class sdl_ttf
{
public:
sdl_ttf();
sdl_ttf(sdl_ttf&& other) noexcept;
~sdl_ttf();
auto operator=(sdl_ttf&& other) noexcept->sdl_ttf&;
// Non-copyable
sdl_ttf(sdl_ttf const&) = delete;
auto operator=(sdl_ttf const&)->sdl_ttf& = delete;
friend void _swap(sdl_ttf& a, sdl_ttf& b) noexcept;
private:
bool _own = false;
};
struct sdl_deleter
{
void operator()(SDL_Window* p) noexcept;
void operator()(SDL_Renderer* p) noexcept;
void operator()(TTF_Font* p) noexcept;
};
SDLInit.cpp
#include <iostream>
#include <SDL.h>
#include <SDL_image.h>
#include <SDL_ttf.h>
#include "SDLInit.h"
//Initialize SDL
sdl::sdl()
{
auto const result = SDL_Init(SDL_INIT_VIDEO);
if (result != 0)
std::cout << "SDL could not initialize";
}
sdl::sdl(sdl&& other) noexcept
{
_swap(*this, other);
}
sdl::~sdl()
{
if (_own)
SDL_Quit();
}
auto sdl::operator=(sdl&& other) noexcept -> sdl&
{
_swap(*this, other);
return *this;
}
void _swap(sdl& a, sdl& b) noexcept
{
using std::swap;
swap(a._own, b._own);
}
//Initialize SDL Image
sdl_img::sdl_img()
{
auto const result = IMG_Init(IMG_INIT_PNG);
if (result == 0)
std::cout << "SDL Image could not initialize";
}
sdl_img::sdl_img(sdl_img&& other) noexcept
{
_swap(*this, other);
}
sdl_img::~sdl_img()
{
if (_own)
IMG_Quit();
}
auto sdl_img::operator=(sdl_img&& other) noexcept -> sdl_img&
{
_swap(*this, other);
return *this;
}
void _swap(sdl_img& a, sdl_img& b) noexcept
{
using std::swap;
swap(a._own, b._own);
}
//Initialize SDL True Type Fonts
sdl_ttf::sdl_ttf()
{
auto const result = TTF_Init();
if (result != 0)
std::cout << "SDL TTF could not initialize";
}
sdl_ttf::sdl_ttf(sdl_ttf&& other) noexcept
{
_swap(*this, other);
}
sdl_ttf::~sdl_ttf()
{
if (_own)
TTF_Quit();
}
auto sdl_ttf::operator=(sdl_ttf&& other) noexcept -> sdl_ttf&
{
_swap(*this, other);
return *this;
}
void _swap(sdl_ttf& a, sdl_ttf& b) noexcept
{
using std::swap;
swap(a._own, b._own);
}
//used with std::unique_ptr
void sdl_deleter::operator()(SDL_Window* p) noexcept
{
if (p)
SDL_DestroyWindow(p);
}
void sdl_deleter::operator()(SDL_Renderer* p) noexcept
{
if (p)
SDL_DestroyRenderer(p);
}
void sdl_deleter::operator()(TTF_Font* p) noexcept
{
if (p)
TTF_CloseFont(p);
}
Texture.h
#pragma once
#include <SDL.h>
//used to tag how the texture was rendered
enum TextureType { from_surface, from_text };
class LTexture
{
public:
LTexture();
LTexture(TextureType type, SDL_Renderer* renderer, std::string const& path);
LTexture(TextureType type, TTF_Font* font, SDL_Renderer* renderer, std::string const& text, SDL_Color color, int width);
LTexture(LTexture&& other) noexcept
{
swap(*this, other);
}
~LTexture();
void render(SDL_Renderer* renderer, int x, int y, SDL_Rect* clip = nullptr, double angle = 0.0, SDL_Point* center = nullptr, SDL_RendererFlip flip = SDL_FLIP_NONE);
void loadfromsurface(SDL_Renderer* renderer, std::string const& path);
void loadfromtext(TTF_Font* font, SDL_Renderer* renderer, std::string const& text, SDL_Color color, int width);
LTexture& operator=(LTexture&& other) noexcept
{
swap(*this, other);
return *this;
}
friend void swap(LTexture& a, LTexture& b) noexcept;
SDL_Texture* mTexture = nullptr;
int mWidth = 0;
int mHeight = 0;
SDL_Rect mButton = {};
//had to make default in order to use in another class
LTexture(LTexture const&) = default;
//LTexture(LTexture const&) = delete;
auto operator=(LTexture const&) = delete;
};
Texture.cpp
#include <iostream>
#include <SDL.h>
#include <SDL_image.h>
#include <SDL_ttf.h>
#include "Texture.h"
LTexture::LTexture()
{
}
LTexture::LTexture(TextureType type, SDL_Renderer* renderer, std::string const& path)
{
SDL_Surface *surface = IMG_Load(path.c_str());
if (!surface)
std::cout << "Failed to create surface";
mTexture = SDL_CreateTextureFromSurface(renderer, surface);
if (!mTexture)
std::cout << "Failed to create texture";
mWidth = surface->w;
mHeight = surface->h;
SDL_FreeSurface(surface);
}
LTexture::LTexture(TextureType type, TTF_Font* font, SDL_Renderer* renderer, std::string const& text, SDL_Color color, int width)
{
SDL_Surface* textSurface = TTF_RenderText_Blended_Wrapped(font, text.c_str(), color, width);
if (!textSurface)
std::cout << "Failed to create surface";
mTexture = SDL_CreateTextureFromSurface(renderer, textSurface);
if (!mTexture)
std::cout << "Failed to created texture";
mWidth = textSurface->w;
mHeight = textSurface->h;
SDL_FreeSurface(textSurface);
}
void LTexture::render(SDL_Renderer* renderer, int x, int y, SDL_Rect* clip, double angle, SDL_Point* center, SDL_RendererFlip flip)
{
SDL_Rect destRect = { x, y, mWidth, mHeight };
if (clip != nullptr)
{
destRect.w = clip->w;
destRect.h = clip->h;
}
SDL_RenderCopyEx(renderer, mTexture, clip, &destRect, angle, center, flip);
//create a rectangle that coincides with texture to check for button presses
mButton = { x, y, destRect.w, destRect.h };
}
void LTexture::loadfromsurface(SDL_Renderer* renderer, std::string const& path)
{
SDL_Surface *surface = IMG_Load(path.c_str());
if (!surface)
std::cout << "Failed to create surface";
mTexture = SDL_CreateTextureFromSurface(renderer, surface);
if (!mTexture)
std::cout << "Failed to create texture";
mWidth = surface->w;
mHeight = surface->h;
SDL_FreeSurface(surface);
}
void LTexture::loadfromtext(TTF_Font* font, SDL_Renderer* renderer, std::string const& text, SDL_Color color, int width)
{
SDL_Surface* textSurface = TTF_RenderText_Blended_Wrapped(font, text.c_str(), color, width);
if (!textSurface)
std::cout << "Failed to create surface";
mTexture = SDL_CreateTextureFromSurface(renderer, textSurface);
if (!mTexture)
std::cout << "Failed to created texture";
mWidth = textSurface->w;
mHeight = textSurface->h;
SDL_FreeSurface(textSurface);
}
void swap(LTexture& a, LTexture& b) noexcept
{
using std::swap;
swap(a.mTexture, b.mTexture);
swap(a.mWidth, b.mWidth);
swap(a.mHeight, b.mHeight);
swap(a.mButton, b.mButton);
}
LTexture::~LTexture()
{
if (mTexture)
SDL_DestroyTexture(mTexture);
}
The two main issues I had making the game were:
I've tried making my texture wrapping using this guide which calls for the deleting the copy functions. However, I wanted to make a type of
AssetManager
class that I could load all of the textures into for easy use. But because the copy function for theLTexture
class was deleted I would just get an error about referencing a deleted function. This resulted in me using the default copy function.So my question is basically, is there another way to use a resource wrapper within a different class without having to use the default copy function?
While making the
playAgain
game state I had an issue where once the last move was made (either a winning move, or a move that took the last remaining open slot) the last mark wouldn't render and the game would just move to theplayAgain
state. I tried moving the the order of the functions around hoping it would change anything but it didn't. I ended up having to just call thegameLoop_render
function within the gameLoop_update to allow for the final mark to be shown. I realize I could have just completely redrawn the board from within theplayAgain_render
function, but I was trying to stay away from duplicating all of that code.