Skip to content

Instantly share code, notes, and snippets.

@MORTAL2000
Last active October 23, 2019 10:39
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save MORTAL2000/c8014042a65985a568b5dd7c2f635cab to your computer and use it in GitHub Desktop.
Save MORTAL2000/c8014042a65985a568b5dd7c2f635cab 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 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