Last active
December 18, 2017 05:42
-
-
Save MORTAL2000/2ba8479330e554b4f08e 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 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, | |
}; | |
unsigned getRandom(unsigned low, unsigned high) | |
{ | |
static std::default_random_engine engine{}; | |
using Dist = std::uniform_int_distribution<unsigned>; | |
static Dist random{}; | |
return random(engine, Dist::param_type{ low, high }); | |
} | |
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.setParameter("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.setParameter("source", input.getTexture()); | |
gaussianBlur.setParameter("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.setParameter("source", input.getTexture()); | |
downSampler.setParameter("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.setParameter("source", source.getTexture()); | |
adder.setParameter("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) | |
{ | |
mColor = player; | |
} | |
sf::Vector3f getLightColor() const | |
{ | |
return mColor; | |
} | |
private: | |
Player mOwner = Player::None; | |
sf::Vector3f mColor = sf::Vector3f(0, 0, 0); | |
}; | |
class World : private sf::NonCopyable | |
{ | |
public: | |
explicit World(sf::RenderTarget& outputTarget); | |
bool isFull() const; | |
bool isWinner(Player player) const; | |
bool applyMove(Player player, sf::Uint32 row, sf::Uint32 column); | |
bool applyAl(Player player); | |
void draw(); | |
private: | |
bool tryCenter(); | |
bool tryBlocking(); | |
bool tryCorners(); | |
sf::RenderTarget& mTarget; | |
sf::RenderTexture mRenderTexture; | |
sf::Sprite mSpriteWorld; | |
ShaderHolder mShader; | |
BloomEffect mBloomEffect; | |
unsigned mRemain; | |
std::array<Tile, DIM * DIM> mTiles; | |
}; | |
World::World(sf::RenderTarget& outputTarget) | |
: mTarget(outputTarget) | |
, mRenderTexture() | |
, mTiles() | |
, mRemain(DIM * DIM) | |
{ | |
sf::Vector2f startPosition(START_POINT); | |
for (unsigned i = 0; i < DIM; ++i) | |
{ | |
for (unsigned j = 0; j < DIM; ++j) | |
{ | |
unsigned 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; | |
} | |
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).setParameter("ScreenResolution", sf::Vector2f(WINDOW_SIZE.x * 1.f, WINDOW_SIZE.y * 1.f)); | |
} | |
void World::draw() | |
{ | |
if (PostEffect::isSupported()) | |
{ | |
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); | |
mShader.get(Shaders::Light).setParameter("LightOrigin", position); | |
mShader.get(Shaders::Light).setParameter("SourceColor", tile.getLightColor()); | |
mShader.get(Shaders::Light).setParameter("LightAttenuation", 20); | |
sf::RenderStates states; | |
states.shader = &mShader.get(Shaders::Light); | |
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) | |
{ | |
unsigned position = row + DIM * column; | |
if ((position > mTiles.size()) || (mTiles[position].getOwner() != Player::None) || row >= DIM || column >= DIM) | |
{ | |
return false; | |
} | |
--mRemain; | |
mTiles[position].setOwner(player); | |
switch (player) | |
{ | |
case Player::User: | |
mTiles[position].setLightColor(sf::Vector3f(0, 0, 255)); | |
break; | |
case Player::Computer: | |
mTiles[position].setLightColor(sf::Vector3f(255, 0, 0)); | |
break; | |
} | |
return true; | |
} | |
bool World::isFull() const | |
{ | |
return (mRemain == 0); | |
} | |
bool World::applyAl(Player player) | |
{ | |
if (tryCenter()) | |
return true; | |
if (tryBlocking()) | |
return true; | |
if (tryCorners()) | |
return true; | |
unsigned row = getRandom(0, DIM - 1); | |
unsigned col = getRandom(0, DIM - 1); | |
if (applyMove(player, row, col)) | |
return true; | |
return false; | |
} | |
bool World::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].setLightColor(sf::Vector3f(255, 0, 0)); | |
--mRemain; | |
return true; | |
} | |
return false; | |
} | |
bool World::tryBlocking() | |
{ | |
if (DIM != 3) | |
return false; | |
std::array<bool, DIM> win; | |
std::array<std::vector<int>, DIM> block; | |
win.fill(true); | |
unsigned j = 0; | |
for (const auto& i : mTiles) | |
{ | |
unsigned 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()].setLightColor(sf::Vector3f(255, 0, 0)); | |
--mRemain; | |
return true; | |
} | |
} | |
} | |
return false; | |
} | |
bool World::tryCorners() | |
{ | |
if (mTiles[0].getOwner() == Player::None) | |
{ | |
mTiles[0].setOwner(Player::Computer); | |
mTiles[0].setLightColor(sf::Vector3f(255, 0, 0)); | |
--mRemain; | |
return true; | |
} | |
if (mTiles[DIM - 1].getOwner() == Player::None) | |
{ | |
mTiles[DIM - 1].setOwner(Player::Computer); | |
mTiles[DIM - 1].setLightColor(sf::Vector3f(255, 0, 0)); | |
--mRemain; | |
return true; | |
} | |
if (mTiles[2 * DIM].getOwner() == Player::None) | |
{ | |
mTiles[2 * DIM].setOwner(Player::Computer); | |
mTiles[2 * DIM].setLightColor(sf::Vector3f(255, 0, 0)); | |
--mRemain; | |
return true; | |
} | |
if (mTiles[DIM * DIM - 1].getOwner() == Player::None) | |
{ | |
mTiles[DIM * DIM - 1].setOwner(Player::Computer); | |
mTiles[DIM * DIM - 1].setLightColor(sf::Vector3f(255, 0, 0)); | |
--mRemain; | |
return true; | |
} | |
return false; | |
} | |
bool World::isWinner(Player player) const | |
{ | |
std::array<bool, 2 * (DIM + 1)> win; | |
win.fill(true); | |
unsigned j = 0; | |
for (const auto& i : mTiles) | |
{ | |
unsigned 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 : 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; | |
unsigned mPlayer; | |
}; | |
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 } }) | |
, mPlayer() | |
{ | |
mFont.load(Fonts::Main, "Sansation.ttf"); | |
mText.setFont(mFont.get(Fonts::Main)); | |
mText.setStyle(sf::Text::Bold); | |
mText.setCharacterSize(20); | |
mText.setColor(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.setColor(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() | |
{ | |
if (mWorld.isWinner(mPlayers[mPlayer])) | |
{ | |
mText.setString("The Winner: " + std::string((mPlayers[mPlayer] == Player::User) ? "Blues" : "Reds")); | |
return; | |
} | |
if (mWorld.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); | |
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(mPlayers[mPlayer], row, col) && !mWorld.isWinner(mPlayers[mPlayer])) | |
mPlayer ^= 1; | |
} | |
} | |
break; | |
case Player::Computer: | |
if (mWorld.applyAl(mPlayers[mPlayer]) && !mWorld.isWinner(mPlayers[mPlayer])) | |
mPlayer ^= 1; | |
break; | |
} | |
} | |
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