Skip to content

Instantly share code, notes, and snippets.

@MORTAL2000
Last active February 6, 2023 16:20
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save MORTAL2000/c5beb7b5f65a25400434 to your computer and use it in GitHub Desktop.
Save MORTAL2000/c5beb7b5f65a25400434 to your computer and use it in GitHub Desktop.
#include <SFML/Graphics.hpp>
#include <iostream>
#include <string>
#include <vector>
#include <array>
#include <random>
enum struct Player : sf::Uint32
{
None,
User,
Computer
};
namespace
{
const sf::Vector2i WINDOW_SIZE(640, 480);
const sf::Uint32 NUMBER_OF_PLAYERS = 2;
const sf::Uint32 DIM = 3;
const float SIZE = 70.f;
const sf::Vector2f START_POINT(WINDOW_SIZE.x * 0.5f - DIM * SIZE * 0.5f, WINDOW_SIZE.y * 0.5f - DIM * SIZE * 0.5f);
sf::Uint32 getRandom(sf::Uint32 low, sf::Uint32 high)
{
static std::default_random_engine engine{};
using Dist = std::uniform_int_distribution < sf::Uint32 >;
static Dist random{};
return random(engine, Dist::param_type{ low, high });
}
}
class Tile final : public sf::RectangleShape
{
public:
Tile() = default;
void setOwner(Player player)
{
mOwner = player;
}
Player getOwner() const
{
return mOwner;
}
private:
Player mOwner = Player::None;
};
class TicTacToe final : public sf::Drawable
{
public:
TicTacToe();
bool isFull() const;
bool isWinner(Player player) const;
bool applyMove(Player player, sf::Uint32 row, sf::Uint32 column);
bool applyAl(Player player);
private:
void draw(sf::RenderTarget &target, sf::RenderStates states) const override;
bool tryCenter();
bool tryBlocking();
bool tryCorners();
sf::Uint32 mRemain;
std::array<Tile, DIM * DIM> mTiles;
};
TicTacToe::TicTacToe()
: mTiles()
, mRemain(DIM * DIM)
{
sf::Vector2f startPosition(START_POINT);
for (sf::Uint32 i = 0; i < DIM; ++i)
{
for (sf::Uint32 j = 0; j < DIM; ++j)
{
sf::Uint32 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, sf::Uint32 row, sf::Uint32 column)
{
sf::Uint32 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;
}
sf::Uint32 row = getRandom(0, DIM - 1);
sf::Uint32 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);
sf::Uint32 j = 0;
for (const auto& i : mTiles)
{
sf::Uint32 x = j++;
for (sf::Uint32 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);
sf::Uint32 j = 0;
for (const auto& i : mTiles)
{
sf::Uint32 x = j++;
for (sf::Uint32 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;
sf::Uint32 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);
sf::Uint32 row = (mouseWorld.y - START_POINT.y) / SIZE;
sf::Uint32 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;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment