Last active
February 6, 2023 16:20
-
-
Save MORTAL2000/c5beb7b5f65a25400434 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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