Skip to content

Instantly share code, notes, and snippets.

@MORTAL2000
Last active December 18, 2017 05:42
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save MORTAL2000/2ba8479330e554b4f08e to your computer and use it in GitHub Desktop.
Save MORTAL2000/2ba8479330e554b4f08e to your computer and use it in GitHub Desktop.
#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