2
\$\begingroup\$

This is a graphical Tic Tac Toe game by using SFML cross-platform library. It seems to work fine. I'm actually new to SFML and making GUIs. If anyone is familiar with SFML, I'd appreciate some pointers on how to improve it.

#include <SFML/Graphics.hpp>
#include <iostream>
#include <string>
#include <vector>
#include <array>
#include <random>

enum struct Player : std::size_t
{
    None,
    User,
    Computer
};

static const sf::Vector2i WINDOW_SIZE(640, 480);
static const std::size_t NUMBER_OF_PLAYERS = 2;
static const std::size_t DIM = 3;
static const float SIZE = 70.f;
static const sf::Vector2f START_POINT(WINDOW_SIZE.x * 0.5f - DIM * SIZE * 0.5f, WINDOW_SIZE.y * 0.5f - DIM * SIZE * 0.5f);

std::size_t getRandom(std::size_t low, std::size_t high)
{
    static std::default_random_engine engine{};
    using Dist = std::uniform_int_distribution < std::size_t >;
    static Dist random{};
    return random(engine, Dist::param_type{ low, high });
}

class Tile : public sf::RectangleShape
{
public:
    Tile() = default;

    void setOwner(Player player)
    {
        mOwner = player;
    }

    Player getOwner() const
    {
        return mOwner;
    }

private:
    Player mOwner = Player::None;
};

class TicTacToe : public sf::Drawable
{
public:
    TicTacToe();

    bool isFull() const;
    bool isWinner(Player player) const;
    bool applyMove(Player player, std::size_t row, std::size_t column);
    bool applyAl(Player player);

private:
    virtual void draw(sf::RenderTarget &target, sf::RenderStates states) const override;

    bool tryCenter();
    bool tryBlocking();
    bool tryCorners();

    std::size_t mRemain;
    std::array<Tile, DIM * DIM> mTiles;
};

TicTacToe::TicTacToe()
    : mTiles()
    , mRemain(DIM * DIM)
{
    sf::Vector2f startPosition(START_POINT);

    for (std::size_t i = 0; i < DIM; ++i)
    {
        for (std::size_t j = 0; j < DIM; ++j)
        {
            std::size_t position = j * DIM + i;

            mTiles[position].setSize(sf::Vector2f(SIZE, SIZE));
            mTiles[position].setPosition(startPosition);
            mTiles[position].setOutlineThickness(2.f);
            mTiles[position].setFillColor(sf::Color::Black);
            mTiles[position].setOutlineColor(sf::Color::White);

            startPosition.x += SIZE;
        }

        startPosition.y += SIZE;
        startPosition.x = START_POINT.x;
    }
}

void TicTacToe::draw(sf::RenderTarget &target, sf::RenderStates states) const
{
    for (auto&& i : mTiles)
    {
        target.draw(i, states);
    }
}

bool TicTacToe::applyMove(Player player, std::size_t row, std::size_t column)
{
    std::size_t position = row + DIM * column;

    if ((position > mTiles.size()) || (mTiles[position].getOwner() != Player::None))
    {
        return false;
    }

    --mRemain;

    mTiles[position].setOwner(player);

    switch (player)
    {
    case Player::User:
        mTiles[position].setFillColor(sf::Color::Blue);
        break;
    case Player::Computer:
        mTiles[position].setFillColor(sf::Color::Red);
        break;
    }

    return true;
}

bool TicTacToe::isFull() const
{
    return (mRemain == 0);
}

bool TicTacToe::applyAl(Player player)
{
    if (tryCenter())
    {
        return true;
    }

    if (tryBlocking())
    {
        return true;
    }

    if (tryCorners())
    {
        return true;
    }

    std::size_t row = getRandom(0, DIM - 1);
    std::size_t col = getRandom(0, DIM - 1);

    if (applyMove(player, row, col))
    {
        return true;
    }

    return false;
}

bool TicTacToe::tryCenter()
{
    if (DIM % 2 == 0)
    {
        return false;
    }

    if (mTiles[(DIM * DIM - 1) / 2].getOwner() == Player::None)
    {
        mTiles[(DIM * DIM - 1) / 2].setOwner(Player::Computer);
        mTiles[(DIM * DIM - 1) / 2].setFillColor(sf::Color::Red);
        --mRemain;
        return true;
    }

    return false;
}

bool TicTacToe::tryBlocking()
{
    if (DIM != 3)
    {
        return false;
    }

    std::array<bool, DIM> win;
    std::array<std::vector<int>, DIM> block;
    win.fill(true);

    std::size_t j = 0;

    for (const auto& i : mTiles)
    {
        std::size_t x = j++;

        for (std::size_t k = 0; k < DIM; ++k)
        {
            if (x % DIM == k && x < DIM)
            {
                if (win[k] &= i.getOwner() != Player::User)
                {
                    block[0].push_back(x);
                }
            }

            if (x % DIM == k && x >= DIM && x < DIM * 2)
            {
                if (win[k] &= i.getOwner() != Player::User)
                {
                    block[1].push_back(x);
                }
            }

            if (x % DIM == k && x >= DIM * 2)
            {
                if (win[k] &= i.getOwner() != Player::User)
                {
                    block[2].push_back(x);
                }
            }
        }
    }

    for (const auto& i : block)
    {
        if (i.size() == 1)
        {
            if (mTiles[i.back()].getOwner() == Player::None)
            {
                mTiles[i.back()].setOwner(Player::Computer);
                mTiles[i.back()].setFillColor(sf::Color::Red);
                --mRemain;
                return true;
            }
        }
    }

    return false;
}

bool TicTacToe::tryCorners()
{
    if (mTiles[0].getOwner() == Player::None)
    {
        mTiles[0].setOwner(Player::Computer);
        mTiles[0].setFillColor(sf::Color::Red);
        --mRemain;
        return true;
    }

    if (mTiles[DIM - 1].getOwner() == Player::None)
    {
        mTiles[DIM - 1].setOwner(Player::Computer);
        mTiles[DIM - 1].setFillColor(sf::Color::Red);
        --mRemain;
        return true;
    }

    if (mTiles[2 * DIM].getOwner() == Player::None)
    {
        mTiles[2 * DIM].setOwner(Player::Computer);
        mTiles[2 * DIM].setFillColor(sf::Color::Red);
        --mRemain;
        return true;
    }

    if (mTiles[DIM * DIM - 1].getOwner() == Player::None)
    {
        mTiles[DIM * DIM - 1].setOwner(Player::Computer);
        mTiles[DIM * DIM - 1].setFillColor(sf::Color::Red);
        --mRemain;
        return true;
    }

    return false;
}

bool TicTacToe::isWinner(Player player) const
{
    std::array<bool, 2 * (DIM + 1)> win;

    win.fill(true);

    std::size_t j = 0;

    for (const auto& i : mTiles)
    {
        std::size_t x = j++;

        for (std::size_t k = 0; k < DIM; ++k)
        {
            if (x % DIM == k)
            {
                win[k] &= i.getOwner() == player;
            }

            if (x / DIM == k)
            {
                win[DIM + k] &= i.getOwner() == player;
            }

            if ((k == 0 && (x / DIM - x % DIM == k))
                || (k == 1 && (x / DIM + x % DIM == DIM - k)))  
            {
                win[2 * DIM + k] &= i.getOwner() == player;
            }
        }
    }

    for (const auto& i : win)
    {
        if (i)
        {
            return true;
        }
    }

    return false;
}

class Game
{
public:
    Game();
    void run();

private:
    void processEvents();
    void update();
    void render();

    TicTacToe mTicTacToe;
    std::array<Player, NUMBER_OF_PLAYERS> mPlayers;
    std::size_t mPlayer;

    sf::RenderWindow mWindow;
    sf::Font mFont;
    sf::Text mText;
    sf::Text mTitle;
};

Game::Game()
    : mWindow(sf::VideoMode(WINDOW_SIZE.x, WINDOW_SIZE.y), "Tic Tac Toe - SFML")
    , mFont()
    , mText()
    , mTitle()
    , mTicTacToe()
    , mPlayers({ { Player::User, Player::Computer } })
    , mPlayer()
{
    if (!mFont.loadFromFile("Sansation.ttf"))
    {
        throw "Can't load font file";
    }

    mText.setFont(mFont);
    mText.setStyle(sf::Text::Bold);
    mText.setCharacterSize(20);
    mText.setColor(sf::Color::Green);
    mText.setPosition(30.f, mWindow.getSize().y - 50.f);

    mTitle.setString("Colourful Tic Tac Toe");
    mTitle.setFont(mFont);
    mTitle.setStyle(sf::Text::Bold);
    mTitle.setCharacterSize(30);
    mTitle.setColor(sf::Color::Green);
    mTitle.setPosition(mWindow.getSize().y * 0.5f - 60.f, 50.f);
}

void Game::run()
{
    while (mWindow.isOpen())
    {
        processEvents();
        update();
        render();
    }
}

void Game::processEvents()
{
    sf::Event event;

    while (mWindow.pollEvent(event))
    {
        if (event.type == sf::Event::Closed)
        {
            mWindow.close();
        }
    }
}

void Game::update()
{
    if (mTicTacToe.isWinner(mPlayers[mPlayer]))
    {
        mText.setString("The Winner: " + std::string((mPlayers[mPlayer] == Player::User) ? "Blues" : "Reds"));
        return;
    }

    if (mTicTacToe.isFull())
    {
        mText.setString("*** Tie ***");
        return;
    }

    switch (mPlayers[mPlayer])
    {
    case Player::User:
        if (sf::Mouse::isButtonPressed(sf::Mouse::Left))
        {
            sf::Vector2i position = sf::Mouse::getPosition(mWindow);
            sf::Vector2f mouseWorld = mWindow.mapPixelToCoords(position);

            std::size_t row = (mouseWorld.y - START_POINT.y) / SIZE;
            std::size_t col = (mouseWorld.x - START_POINT.x) / SIZE;

            if (mTicTacToe.applyMove(mPlayers[mPlayer], row, col) && !mTicTacToe.isWinner(mPlayers[mPlayer]))
            {
                mPlayer ^= 1;
            }
        }
        break;
    case Player::Computer:
        if (mTicTacToe.applyAl(mPlayers[mPlayer]) && !mTicTacToe.isWinner(mPlayers[mPlayer]))
        {
            mPlayer ^= 1;
        }
        break;
    }
}

void Game::render()
{
    mWindow.clear();
    mWindow.draw(mTicTacToe);
    mWindow.draw(mTitle);
    mWindow.draw(mText);
    mWindow.display();
}

int main()
{
    try
    {
        Game game;
        game.run();
    }
    catch (std::runtime_error& e)
    {
        std::cout << "Exception: " << e.what() << std::endl;
        return 1;
    }
}
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.