Last active
October 23, 2019 10:39
-
-
Save MORTAL2000/c8014042a65985a568b5dd7c2f635cab 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 <memory> | |
#include <map> | |
#include <string> | |
#include <vector> | |
#include <array> | |
#include <random> | |
#include <stdexcept> | |
#include <cassert> | |
namespace | |
{ | |
const std::string shaderCode = \ | |
"uniform vec2 LightOrigin;"\ | |
"uniform vec3 SourceColor;"\ | |
"uniform float LightAttenuation;"\ | |
"uniform vec2 ScreenResolution;"\ | |
"void main()"\ | |
"{"\ | |
" vec2 baseDistance = gl_FragCoord.xy;"\ | |
" baseDistance.y = ScreenResolution.y - baseDistance.y;"\ | |
" vec2 distance = LightOrigin - baseDistance;"\ | |
" float linearDistance = length(distance);"\ | |
" float attenuation = 1.0 / ((LightAttenuation + LightAttenuation) * linearDistance);"\ | |
" vec4 lightColor = vec4(SourceColor, 1.0);"\ | |
" vec4 color = vec4(attenuation, attenuation, attenuation, 1.0) * lightColor;"\ | |
" gl_FragColor = color;"\ | |
"}"; | |
const std::string Brightness = \ | |
"uniform sampler2D source;"\ | |
"const float Threshold = 0.7;"\ | |
"const float Factor = 4.0;"\ | |
"void main()"\ | |
"{"\ | |
" vec4 sourceFragment = texture2D(source, gl_TexCoord[0].xy);"\ | |
" float luminance = sourceFragment.r * 0.2126 + sourceFragment.g * 0.7152 + sourceFragment.b * 0.0722;"\ | |
" sourceFragment *= clamp(luminance - Threshold, 0.0, 1.0) * Factor;"\ | |
" gl_FragColor = sourceFragment;"\ | |
"}"; | |
const std::string GuassianBlur = \ | |
"uniform sampler2D source;"\ | |
"uniform vec2 offsetFactor;"\ | |
"void main()"\ | |
"{"\ | |
" vec2 textureCoordinates = gl_TexCoord[0].xy;"\ | |
" vec4 color = vec4(0.0);"\ | |
" color += texture2D(source, textureCoordinates - 4.0 * offsetFactor) * 0.0162162162;"\ | |
" color += texture2D(source, textureCoordinates - 3.0 * offsetFactor) * 0.0540540541;"\ | |
" color += texture2D(source, textureCoordinates - 2.0 * offsetFactor) * 0.1216216216;"\ | |
" color += texture2D(source, textureCoordinates - offsetFactor) * 0.1945945946;"\ | |
" color += texture2D(source, textureCoordinates) * 0.2270270270;"\ | |
" color += texture2D(source, textureCoordinates + offsetFactor) * 0.1945945946;"\ | |
" color += texture2D(source, textureCoordinates + 2.0 * offsetFactor) * 0.1216216216;"\ | |
" color += texture2D(source, textureCoordinates + 3.0 * offsetFactor) * 0.0540540541;"\ | |
" color += texture2D(source, textureCoordinates + 4.0 * offsetFactor) * 0.0162162162;"\ | |
" gl_FragColor = color;"\ | |
"}"; | |
const std::string DownSample = \ | |
"uniform sampler2D source;"\ | |
"uniform vec2 sourceSize;"\ | |
"void main()"\ | |
"{"\ | |
" vec2 pixelSize = vec2(1.0 / sourceSize.x, 1.0 / sourceSize.y);"\ | |
" vec2 textureCoordinates = gl_TexCoord[0].xy;"\ | |
" vec4 color = texture2D(source, textureCoordinates);"\ | |
" color += texture2D(source, textureCoordinates + vec2(1.0, 0.0) * pixelSize);"\ | |
" color += texture2D(source, textureCoordinates + vec2(-1.0, 0.0) * pixelSize);"\ | |
" color += texture2D(source, textureCoordinates + vec2(0.0, 1.0) * pixelSize);"\ | |
" color += texture2D(source, textureCoordinates + vec2(0.0, -1.0) * pixelSize);"\ | |
" color += texture2D(source, textureCoordinates + vec2(1.0, 1.0) * pixelSize);"\ | |
" color += texture2D(source, textureCoordinates + vec2(-1.0, -1.0) * pixelSize);"\ | |
" color += texture2D(source, textureCoordinates + vec2(1.0, -1.0) * pixelSize);"\ | |
" color += texture2D(source, textureCoordinates + vec2(-1.0, 1.0) * pixelSize);"\ | |
" gl_FragColor = color / 9.0;"\ | |
"}"; | |
const std::string Add = \ | |
"uniform sampler2D source;"\ | |
"uniform sampler2D bloom;"\ | |
"void main()"\ | |
"{"\ | |
" vec4 sourceFragment = texture2D(source, gl_TexCoord[0].xy);"\ | |
" vec4 bloomFragment = texture2D(bloom, gl_TexCoord[0].xy);"\ | |
" gl_FragColor = sourceFragment + bloomFragment;"\ | |
"}"; | |
const std::string Fullpass = \ | |
"void main()"\ | |
"{"\ | |
" gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;"\ | |
" gl_TexCoord[0] = gl_MultiTexCoord0;"\ | |
"}"; | |
} | |
namespace | |
{ | |
const sf::Vector2i WINDOW_SIZE(640, 480); | |
const unsigned NUMBER_OF_PLAYERS = 2; | |
const unsigned DIM = 3; | |
const unsigned MAX_DEPTH = 6; | |
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); | |
} | |
enum struct Player : unsigned | |
{ | |
None, | |
User, | |
Computer | |
}; | |
enum struct Shaders : unsigned | |
{ | |
BrightnessPass, | |
DownSamplePass, | |
GaussianBlurPass, | |
AddPass, | |
Light | |
}; | |
enum struct Fonts : unsigned | |
{ | |
Main, | |
}; | |
template <typename Resource> | |
void centerOrigin(Resource& resource) | |
{ | |
sf::FloatRect bounds = resource.getLocalBounds(); | |
resource.setOrigin(std::floor(bounds.left + bounds.width / 2.f), std::floor(bounds.top + bounds.height / 2.f)); | |
} | |
template <typename Resource, typename Identifier> | |
class ResourceHolder | |
{ | |
public: | |
void load(Identifier id, const std::string& filename); | |
template <typename Parameter> | |
void load(Identifier id, const std::string& filename, const Parameter& secondParam, bool flag = true); | |
Resource& get(Identifier id); | |
const Resource& get(Identifier id) const; | |
private: | |
void insertResource(Identifier id, std::unique_ptr<Resource> resource); | |
std::map<Identifier, std::unique_ptr<Resource>> mResourceMap; | |
}; | |
template <typename Resource, typename Identifier> | |
void ResourceHolder<Resource, Identifier>::load(Identifier id, const std::string& filename) | |
{ | |
std::unique_ptr<Resource> resource(std::make_unique<Resource>()); | |
if (!resource->loadFromFile(filename)) | |
throw std::runtime_error("ResourceHolder::load - Failed to load " + filename); | |
insertResource(id, std::move(resource)); | |
} | |
template <typename Resource, typename Identifier> | |
template <typename Parameter> | |
void ResourceHolder<Resource, Identifier>::load(Identifier id, const std::string& filename, const Parameter& secondParam, bool flag) | |
{ | |
std::unique_ptr<Resource> resource(std::make_unique<Resource>()); | |
if (flag) | |
{ | |
if (!resource->loadFromMemory(filename, secondParam)) | |
throw std::runtime_error("ResourceHolder::load - Failed to load from Memory " + filename); | |
} | |
else | |
{ | |
if (!resource->loadFromFile(filename, secondParam)) | |
throw std::runtime_error("ResourceHolder::load - Failed to load " + filename); | |
} | |
insertResource(id, std::move(resource)); | |
} | |
template <typename Resource, typename Identifier> | |
Resource& ResourceHolder<Resource, Identifier>::get(Identifier id) | |
{ | |
auto found = mResourceMap.find(id); | |
assert(found != mResourceMap.end()); | |
return *found->second; | |
} | |
template <typename Resource, typename Identifier> | |
const Resource& ResourceHolder<Resource, Identifier>::get(Identifier id) const | |
{ | |
auto found = mResourceMap.find(id); | |
assert(found != mResourceMap.end()); | |
return *found->second; | |
} | |
template <typename Resource, typename Identifier> | |
void ResourceHolder<Resource, Identifier>::insertResource(Identifier id, std::unique_ptr<Resource> resource) | |
{ | |
auto inserted = mResourceMap.insert(std::make_pair(id, std::move(resource))); | |
assert(inserted.second); | |
} | |
using FontHolder = ResourceHolder<sf::Font, Fonts>; | |
using ShaderHolder = ResourceHolder<sf::Shader, Shaders>; | |
class PostEffect : sf::NonCopyable | |
{ | |
public: | |
virtual ~PostEffect(); | |
virtual void apply(const sf::RenderTexture& input, sf::RenderTarget& output) = 0; | |
static bool isSupported(); | |
protected: | |
static void applyShader(const sf::Shader& shader, sf::RenderTarget& output); | |
}; | |
PostEffect::~PostEffect() | |
{ | |
} | |
void PostEffect::applyShader(const sf::Shader& shader, sf::RenderTarget& output) | |
{ | |
sf::Vector2f outputSize = static_cast<sf::Vector2f>(output.getSize()); | |
sf::VertexArray vertices(sf::TrianglesStrip, 4); | |
vertices[0] = sf::Vertex(sf::Vector2f(0, 0), sf::Vector2f(0, 1)); | |
vertices[1] = sf::Vertex(sf::Vector2f(outputSize.x, 0), sf::Vector2f(1, 1)); | |
vertices[2] = sf::Vertex(sf::Vector2f(0, outputSize.y), sf::Vector2f(0, 0)); | |
vertices[3] = sf::Vertex(sf::Vector2f(outputSize), sf::Vector2f(1, 0)); | |
sf::RenderStates states; | |
states.shader = &shader; | |
states.blendMode = sf::BlendNone; | |
output.draw(vertices, states); | |
} | |
bool PostEffect::isSupported() | |
{ | |
return sf::Shader::isAvailable(); | |
} | |
class BloomEffect final : public PostEffect | |
{ | |
public: | |
using RenderTextureArray = std::array<sf::RenderTexture, 2>; | |
BloomEffect(); | |
void apply(const sf::RenderTexture& input, sf::RenderTarget& output) override; | |
private: | |
void prepareTextures(sf::Vector2u size); | |
void filterBright(const sf::RenderTexture& input, sf::RenderTexture& output); | |
void blurMultipass(RenderTextureArray& renderTextures); | |
void blur(const sf::RenderTexture& input, sf::RenderTexture& output, sf::Vector2f offsetFactor); | |
void downsample(const sf::RenderTexture& input, sf::RenderTexture& output); | |
void add(const sf::RenderTexture& source, const sf::RenderTexture& bloom, sf::RenderTarget& target); | |
ShaderHolder mShaders; | |
sf::RenderTexture mBrightnessTexture; | |
RenderTextureArray mFirstPassTextures; | |
RenderTextureArray mSecondPassTextures; | |
}; | |
BloomEffect::BloomEffect() | |
: mShaders() | |
, mBrightnessTexture() | |
, mFirstPassTextures() | |
, mSecondPassTextures() | |
{ | |
mShaders.load(Shaders::BrightnessPass, Fullpass, Brightness); | |
mShaders.load(Shaders::DownSamplePass, Fullpass, DownSample); | |
mShaders.load(Shaders::GaussianBlurPass, Fullpass, GuassianBlur); | |
mShaders.load(Shaders::AddPass, Fullpass, Add); | |
} | |
void BloomEffect::apply(const sf::RenderTexture& input, sf::RenderTarget& output) | |
{ | |
prepareTextures(input.getSize()); | |
filterBright(input, mBrightnessTexture); | |
downsample(mBrightnessTexture, mFirstPassTextures[0]); | |
blurMultipass(mFirstPassTextures); | |
downsample(mFirstPassTextures[0], mSecondPassTextures[0]); | |
blurMultipass(mSecondPassTextures); | |
add(mFirstPassTextures[0], mSecondPassTextures[0], mFirstPassTextures[1]); | |
mFirstPassTextures[1].display(); | |
add(input, mFirstPassTextures[1], output); | |
} | |
void BloomEffect::prepareTextures(sf::Vector2u size) | |
{ | |
if (mBrightnessTexture.getSize() != size) | |
{ | |
mBrightnessTexture.create(size.x, size.y); | |
mBrightnessTexture.setSmooth(true); | |
mFirstPassTextures[0].create(size.x / 2, size.y / 2); | |
mFirstPassTextures[0].setSmooth(true); | |
mFirstPassTextures[1].create(size.x / 2, size.y / 2); | |
mFirstPassTextures[1].setSmooth(true); | |
mSecondPassTextures[0].create(size.x / 4, size.y / 4); | |
mSecondPassTextures[0].setSmooth(true); | |
mSecondPassTextures[1].create(size.x / 4, size.y / 4); | |
mSecondPassTextures[1].setSmooth(true); | |
} | |
} | |
void BloomEffect::filterBright(const sf::RenderTexture& input, sf::RenderTexture& output) | |
{ | |
sf::Shader& brightness = mShaders.get(Shaders::BrightnessPass); | |
brightness.setUniform("source", input.getTexture()); | |
applyShader(brightness, output); | |
output.display(); | |
} | |
void BloomEffect::blurMultipass(RenderTextureArray& renderTextures) | |
{ | |
sf::Vector2u textureSize = renderTextures[0].getSize(); | |
for (std::size_t count = 0; count < 2; ++count) | |
{ | |
blur(renderTextures[0], renderTextures[1], sf::Vector2f(0.f, 1.f / textureSize.y)); | |
blur(renderTextures[1], renderTextures[0], sf::Vector2f(1.f / textureSize.x, 0.f)); | |
} | |
} | |
void BloomEffect::blur(const sf::RenderTexture& input, sf::RenderTexture& output, sf::Vector2f offsetFactor) | |
{ | |
sf::Shader& gaussianBlur = mShaders.get(Shaders::GaussianBlurPass); | |
gaussianBlur.setUniform("source", input.getTexture()); | |
gaussianBlur.setUniform("offsetFactor", offsetFactor); | |
applyShader(gaussianBlur, output); | |
output.display(); | |
} | |
void BloomEffect::downsample(const sf::RenderTexture& input, sf::RenderTexture& output) | |
{ | |
sf::Shader& downSampler = mShaders.get(Shaders::DownSamplePass); | |
downSampler.setUniform("source", input.getTexture()); | |
downSampler.setUniform("sourceSize", sf::Vector2f(input.getSize())); | |
applyShader(downSampler, output); | |
output.display(); | |
} | |
void BloomEffect::add(const sf::RenderTexture& source, const sf::RenderTexture& bloom, sf::RenderTarget& output) | |
{ | |
sf::Shader& adder = mShaders.get(Shaders::AddPass); | |
adder.setUniform("source", source.getTexture()); | |
adder.setUniform("bloom", bloom.getTexture()); | |
applyShader(adder, output); | |
} | |
class Tile : public sf::RectangleShape, private sf::NonCopyable | |
{ | |
public: | |
Tile() = default; | |
void setOwner(Player player) | |
{ | |
mOwner = player; | |
} | |
Player getOwner() const | |
{ | |
return mOwner; | |
} | |
void setLightColor(sf::Vector3f player) | |
{ | |
mLightColor = player; | |
} | |
sf::Vector3f getLightColor() const | |
{ | |
return mLightColor; | |
} | |
private: | |
Player mOwner = Player::None; | |
sf::Vector3f mLightColor = sf::Vector3f(0, 0, 0); | |
}; | |
class World : private sf::NonCopyable | |
{ | |
struct Move | |
{ | |
unsigned x = 0; | |
unsigned y = 0; | |
}; | |
public: | |
explicit World(sf::RenderTarget& outputTarget); | |
bool isFull() const; | |
bool isWinner(Player player) const; | |
bool applyMove(Player player, sf::Uint32 row, sf::Uint32 column) const; | |
bool applyAl(Player player) const; | |
void draw(); | |
private: | |
Move minimax() const; | |
int minSearch(int level, int alpha, int beta) const; | |
int maxSearch(int level, int alpha, int beta) const; | |
sf::RenderTarget& mTarget; | |
sf::RenderTexture mRenderTexture; | |
sf::Sprite mSpriteWorld; | |
ShaderHolder mShader; | |
BloomEffect mBloomEffect; | |
bool mIsPostEffectSupported; | |
mutable unsigned mRemain; | |
mutable std::array<Tile, DIM * DIM> mTiles; | |
}; | |
World::World(sf::RenderTarget& outputTarget) | |
: mTarget(outputTarget) | |
, mRenderTexture() | |
, mTiles() | |
, mRemain(DIM * DIM) | |
, mIsPostEffectSupported(PostEffect::isSupported()) | |
{ | |
sf::Vector2f startPosition(START_POINT); | |
for (unsigned i = 0; i < DIM; ++i) | |
{ | |
for (unsigned j = 0; j < DIM; ++j) | |
{ | |
unsigned index = j * DIM + i; | |
mTiles[index].setSize(sf::Vector2f(SIZE, SIZE)); | |
mTiles[index].setPosition(startPosition); | |
mTiles[index].setOutlineThickness(2.f); | |
mTiles[index].setFillColor(sf::Color::Black); | |
mTiles[index].setOutlineColor(sf::Color::White); | |
startPosition.x += SIZE; | |
} | |
startPosition.y += SIZE; | |
startPosition.x = START_POINT.x; | |
} | |
mRenderTexture.create(WINDOW_SIZE.x, WINDOW_SIZE.y); | |
mRenderTexture.setSmooth(true); | |
mSpriteWorld.setTexture(mRenderTexture.getTexture()); | |
mSpriteWorld.setOrigin(mSpriteWorld.getTextureRect().width / 2.f, mSpriteWorld.getTextureRect().height / 2.f); | |
mSpriteWorld.setPosition(WINDOW_SIZE.x / 2.f, WINDOW_SIZE.y / 2.f); | |
mShader.load(Shaders::Light, shaderCode, sf::Shader::Fragment); | |
mShader.get(Shaders::Light).setUniform("ScreenResolution", sf::Vector2f(WINDOW_SIZE.x * 1.f, WINDOW_SIZE.y * 1.f)); | |
mShader.get(Shaders::Light).setUniform("LightAttenuation", 20.0f); | |
} | |
void World::draw() | |
{ | |
if (mIsPostEffectSupported) | |
{ | |
mRenderTexture.clear(); | |
for (const auto& tile : mTiles) | |
{ | |
mRenderTexture.draw(tile); | |
sf::Vector2f position(tile.getPosition().x + tile.getLocalBounds().width / 2, tile.getPosition().y + tile.getLocalBounds().height / 2); | |
auto& shader = mShader.get(Shaders::Light); | |
shader.setUniform("LightOrigin", position); | |
shader.setUniform("SourceColor", tile.getLightColor()); | |
sf::RenderStates states; | |
states.shader = &shader; | |
states.blendMode = sf::BlendAdd; | |
mRenderTexture.draw(mSpriteWorld, states); | |
} | |
mRenderTexture.display(); | |
mTarget.clear(); | |
mTarget.draw(mSpriteWorld); | |
mBloomEffect.apply(mRenderTexture, mTarget); | |
} | |
else | |
{ | |
for (const auto& tile : mTiles) | |
{ | |
mTarget.draw(tile); | |
} | |
} | |
} | |
bool World::applyMove(Player player, sf::Uint32 row, sf::Uint32 column) const | |
{ | |
unsigned index = row + DIM * column; | |
if ((index >= mTiles.size()) || (mTiles[index].getOwner() != Player::None) || row >= DIM || column >= DIM) | |
{ | |
return false; | |
} | |
--mRemain; | |
mTiles[index].setOwner(player); | |
switch (player) | |
{ | |
case Player::User: | |
if(mIsPostEffectSupported) | |
mTiles[index].setLightColor(sf::Vector3f(0, 0, 255)); | |
else | |
mTiles[index].setFillColor(sf::Color::Blue); | |
break; | |
case Player::Computer: | |
if (mIsPostEffectSupported) | |
mTiles[index].setLightColor(sf::Vector3f(255, 0, 0)); | |
else | |
mTiles[index].setFillColor(sf::Color::Red); | |
break; | |
} | |
return true; | |
} | |
bool World::isFull() const | |
{ | |
return (mRemain == 0); | |
} | |
bool World::applyAl(Player player) const | |
{ | |
Move move = minimax(); | |
return applyMove(player, move.x, move.y); | |
} | |
World::Move World::minimax() const | |
{ | |
static int level = 0; | |
int score = std::numeric_limits<int>::max(); | |
Move move; | |
for (unsigned i = 0; i < DIM; i++) | |
{ | |
for (unsigned j = 0; j < DIM; j++) | |
{ | |
unsigned index = j * DIM + i; | |
if (mTiles[index].getOwner() == Player::None) | |
{ | |
mTiles[index].setOwner(Player::Computer); | |
--mRemain; | |
int temp = maxSearch(level, std::numeric_limits<int>::min(), std::numeric_limits<int>::max()); | |
if (temp < score) | |
{ | |
score = temp; | |
move.x = i; | |
move.y = j; | |
} | |
mTiles[index].setOwner(Player::None); | |
++mRemain; | |
} | |
} | |
} | |
return move; | |
} | |
int World::maxSearch(int level, int alpha, int beta) const | |
{ | |
if (level >= MAX_DEPTH) { return 0; } | |
if (isWinner(Player::User)) { return 10; } | |
else if (isWinner(Player::Computer)) { return -10; } | |
else if (isFull()) { return 0; } | |
int score = std::numeric_limits<int>::min(); | |
for (unsigned i = 0; i < DIM; i++) | |
{ | |
for (unsigned j = 0; j < DIM; j++) | |
{ | |
unsigned index = j * DIM + i; | |
if (mTiles[index].getOwner() == Player::None) | |
{ | |
mTiles[index].setOwner(Player::User); | |
--mRemain; | |
score = std::max(score, minSearch(level + 1, alpha, beta) - level); | |
alpha = std::max(alpha, score); | |
mTiles[index].setOwner(Player::None); | |
++mRemain; | |
if (beta <= alpha) return alpha; | |
} | |
} | |
} | |
return score; | |
} | |
int World::minSearch(int level, int alpha, int beta) const | |
{ | |
if (level >= MAX_DEPTH) { return 0; } | |
if (isWinner(Player::User)) { return 10; } | |
else if (isWinner(Player::Computer)) { return -10; } | |
else if (isFull()) { return 0; } | |
int score = std::numeric_limits<int>::max(); | |
for (unsigned i = 0; i < DIM; i++) | |
{ | |
for (unsigned j = 0; j < DIM; j++) | |
{ | |
unsigned index = j * DIM + i; | |
if (mTiles[index].getOwner() == Player::None) | |
{ | |
mTiles[index].setOwner(Player::Computer); | |
--mRemain; | |
score = std::min(score, maxSearch(level + 1, alpha, beta) + level); | |
beta = std::min(beta, score); | |
mTiles[index].setOwner(Player::None); | |
++mRemain; | |
if (beta <= alpha) return beta; | |
} | |
} | |
} | |
return score; | |
} | |
bool World::isWinner(Player player) const | |
{ | |
// check for row or column wins | |
for (unsigned i = 0; i < DIM; ++i) | |
{ | |
bool rowwin = true; | |
bool colwin = true; | |
for (unsigned j = 0; j < DIM; ++j) | |
{ | |
rowwin &= mTiles[i*DIM + j].getOwner() == player; | |
colwin &= mTiles[j*DIM + i].getOwner() == player; | |
} | |
if (colwin || rowwin) | |
return true; | |
} | |
// check for diagonal wins | |
bool diagwin = true; | |
for (unsigned i = 0; i < DIM; ++i) | |
diagwin &= mTiles[i*DIM + i].getOwner() == player; | |
if (diagwin) | |
return true; | |
diagwin = true; | |
for (unsigned i = 0; i < DIM; ++i) | |
diagwin &= mTiles[i*DIM + (DIM - i - 1)].getOwner() == player; | |
return diagwin; | |
} | |
class Game : private sf::NonCopyable | |
{ | |
public: | |
Game(); | |
void run(); | |
private: | |
void processEvents(); | |
void update(); | |
void render(); | |
sf::RenderWindow mWindow; | |
FontHolder mFont; | |
sf::Text mText; | |
sf::Text mTitle; | |
World mWorld; | |
std::array<Player, NUMBER_OF_PLAYERS> mPlayers; | |
}; | |
Game::Game() | |
: mWindow(sf::VideoMode(WINDOW_SIZE.x, WINDOW_SIZE.y), "Tic Tac Toe - SFML") | |
, mFont() | |
, mText() | |
, mTitle() | |
, mWorld(mWindow) | |
, mPlayers({ { Player::User, Player::Computer } }) | |
{ | |
mWindow.setVerticalSyncEnabled(true); | |
mFont.load(Fonts::Main, "Sansation.ttf"); | |
mText.setFont(mFont.get(Fonts::Main)); | |
mText.setStyle(sf::Text::Bold); | |
mText.setCharacterSize(20); | |
mText.setFillColor(sf::Color::White); | |
mText.setPosition(30.f, mWindow.getSize().y - 50.f); | |
centerOrigin(mText); | |
mTitle.setString("Colourful Tic Tac Toe"); | |
mTitle.setFont(mFont.get(Fonts::Main)); | |
mTitle.setStyle(sf::Text::Bold); | |
mTitle.setCharacterSize(30); | |
mTitle.setFillColor(sf::Color::White); | |
mTitle.setPosition(mWindow.getSize().x * 0.5f, 50.f); | |
centerOrigin(mTitle); | |
} | |
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() | |
{ | |
static bool winner = false; | |
static bool tie = false; | |
static unsigned index = 0; | |
if (tie || winner) return; | |
switch (mPlayers[index]) | |
{ | |
case Player::User: | |
if (sf::Mouse::isButtonPressed(sf::Mouse::Left)) | |
{ | |
const sf::Vector2i& position = sf::Mouse::getPosition(mWindow); | |
if (position.x > START_POINT.x && | |
position.y > START_POINT.y && | |
position.x < (START_POINT.x + (DIM*SIZE)) && | |
position.y < (START_POINT.y + (DIM*SIZE))) | |
{ | |
unsigned row = static_cast<unsigned>((position.y - START_POINT.y) / SIZE); | |
unsigned col = static_cast<unsigned>((position.x - START_POINT.x) / SIZE); | |
if (mWorld.applyMove(Player::User, row, col)) { | |
winner = mWorld.isWinner(Player::User); | |
if (!winner) { | |
index ^= 1; | |
} | |
} | |
} | |
} | |
break; | |
case Player::Computer: | |
if (mWorld.applyAl(Player::Computer)) { | |
winner = mWorld.isWinner(Player::Computer); | |
if (!winner) { | |
index ^= 1; | |
} | |
} | |
break; | |
} | |
if (winner) | |
{ | |
mText.setString("The Winner: " + std::string((mPlayers[index] == Player::User) ? "Blues" : "Reds")); | |
return; | |
} | |
tie = mWorld.isFull(); | |
if (tie) | |
{ | |
mText.setString("*** Tie ***"); | |
//return; | |
} | |
} | |
void Game::render() | |
{ | |
mWindow.clear(); | |
mWorld.draw(); | |
mWindow.draw(mTitle); | |
mWindow.draw(mText); | |
mWindow.display(); | |
} | |
int main() | |
{ | |
try | |
{ | |
Game game; | |
game.run(); | |
} | |
catch (std::runtime_error& e) | |
{ | |
std::cout << "\nException: " << e.what() << std::endl; | |
return 1; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment