3
\$\begingroup\$

Based on the previous question, I have implemented all suggestions. Also, I worked a bit in this project to improve it.

Summary of improvements:

  • split the code into multiple files as .hpp and .cpp
  • removed STL functions
  • used only one sf::RectangleShape for border instead of std::vector.

I would like to know how I can improve it further.

Utility.hpp

#pragma once
#include <random>
namespace utility
{
 using DistType = std::uniform_real_distribution<float>;
 auto randomEngine();
 float random(DistType& dist);
 float radian(std::size_t direction);
 float degree(std::size_t direction);
}

Utility.cpp

#include "Utility.hpp"
#include <array>
#include <utility>
#include <algorithm>
#include <functional>
auto utility::randomEngine()
{
 std::array<std::mt19937::result_type, std::mt19937::state_size> seed_data;
 thread_local std::random_device source;
 std::generate(std::begin(seed_data), std::end(seed_data), std::ref(source));
 std::seed_seq seeds(std::begin(seed_data), std::end(seed_data));
 thread_local std::mt19937 seeded_engine(seeds);
 return seeded_engine;
}
float utility::random(DistType& dist)
{
 thread_local auto RandomEngine = randomEngine();
 return dist(RandomEngine);
}
float utility::radian(std::size_t direction)
{
 constexpr static auto Ratio = 0.0174532925f; //pi/180
 constexpr static std::array<float, 4> Radians
 {
 180 * Ratio,
 0,
 270 * Ratio,
 90 * Ratio
 };
 return Radians[direction];
}
float utility::degree(std::size_t direction)
{
 constexpr static std::array<float, 4> Degrees
 {
 180,
 0,
 270,
 90
 };
 return Degrees[direction];
}

Snake.hpp

#pragma once
#include <SFML/System/Time.hpp>
#include <SFML/Graphics/Text.hpp>
#include <SFML/System/NonCopyable.hpp>
#include <SFML/Graphics/RectangleShape.hpp>
#include <vector>
namespace sf
{
 class CircleShape;
}
class Snake : public sf::Drawable, sf::NonCopyable
{
public:
 enum Direction
 {
 Up,
 Down,
 Left,
 Right
 };
private:
 using SnakeContainer = std::vector<sf::RectangleShape>;
public:
 explicit Snake(const sf::Font& font, float windowWidth);
 void reset();
 void setDirection(Direction direction);
 float getBlockSize() const;
 bool hasLost() const;
 void lose();
 void extend();
 void update(sf::Time dt);
 sf::FloatRect getGlobalBounds() const;
 bool hasCollideWithFruit(const sf::CircleShape& shape) const;
private:
 void draw(sf::RenderTarget& target, sf::RenderStates states) const override;
 void checkCollision();
 void move(sf::Time dt);
 void shrink(std::size_t index);
private:
 SnakeContainer mSnakeBody;
 Direction mDirection;
 std::size_t mLives;
 sf::Text mLivesText;
 float mBlockSize;
 bool mIsFirstRun;
};

Snake.cpp

#include "Snake.hpp"
#include "Utility.hpp"
#include <SFML/Graphics/CircleShape.hpp>
#include <SFML/Graphics/RenderWindow.hpp>
using namespace utility;
Snake::Snake(const sf::Font& font, float windowWidth) : mBlockSize(16)
{
 reset();
 constexpr auto Offset = 140.f;
 mLivesText.setFont(font);
 mLivesText.setStyle(sf::Text::Bold);
 mLivesText.setCharacterSize(30);
 mLivesText.setColor(sf::Color::White);
 mLivesText.setPosition(windowWidth - Offset, 0.f);
}
void Snake::reset()
{
 mSnakeBody.clear();
 static sf::RectangleShape shape{ { mBlockSize - 1, mBlockSize - 1 } };
 shape.setOrigin(shape.getSize() / 2.f);
 constexpr static auto SnakeLength = 3u;
 constexpr static auto SnakeHead = 0u;
 constexpr static auto SnakeBodyPart = 1u;
 const static auto Position = sf::Vector2f{ 70, 70 };
 for (auto part = 0u; part < SnakeLength; ++part)
 {
 if (part == SnakeHead)
 shape.setFillColor(sf::Color::Yellow);
 else if (part == SnakeBodyPart)
 shape.setFillColor(sf::Color::Green);
 shape.setPosition(Position.x, Position.y + (SnakeLength - part) * mBlockSize);
 mSnakeBody.emplace_back(shape);
 }
 mDirection = Direction::Down;
 mLives = 3;
 mIsFirstRun = true;
 mLivesText.setString("Lives: " + std::to_string(mLives));
}
void Snake::setDirection(Direction direction)
{
 if (std::abs(degree(mDirection) - degree(direction)) == 180) return;
 if (mIsFirstRun) mIsFirstRun = false;
 mDirection = direction;
}
float Snake::getBlockSize() const
{
 return mBlockSize;
}
bool Snake::hasLost() const
{
 return mLives == 0;
}
void Snake::lose()
{
 mLives = 0;
}
void Snake::extend()
{
 static sf::RectangleShape shape{ { mBlockSize - 1, mBlockSize - 1 } };
 shape.setOrigin(shape.getSize() / 2.f);
 const auto& tail = mSnakeBody.back();
 auto x = tail.getPosition().x - std::sin(radian(mDirection)) * mBlockSize;
 auto y = tail.getPosition().y - std::cos(radian(mDirection)) * mBlockSize;
 shape.setPosition(x, y);
 shape.setFillColor(sf::Color::Green);
 mSnakeBody.emplace_back(shape);
}
void Snake::update(sf::Time dt)
{
 if (mIsFirstRun) return;
 checkCollision();
 move(dt);
}
sf::FloatRect Snake::getGlobalBounds() const
{
 return mSnakeBody.front().getGlobalBounds();
}
bool Snake::hasCollideWithFruit(const sf::CircleShape& shape) const
{
 return std::find_if(mSnakeBody.begin(), mSnakeBody.end(),
 [&shape](const auto& part)
 {
 return part.getGlobalBounds().intersects(shape.getGlobalBounds());
 }) != mSnakeBody.end();
}
void Snake::draw(sf::RenderTarget& target, sf::RenderStates states) const
{
 for (const auto& part : mSnakeBody)
 target.draw(part, states);
 target.draw(mLivesText, states);
}
void Snake::checkCollision()
{
 auto begin = mSnakeBody.cbegin();
 auto end = mSnakeBody.cend();
 for (auto it = std::next(begin); it != end; ++it)
 {
 if (!it->getGlobalBounds().contains(begin->getPosition())) continue;
 auto index = std::distance(begin, it);
 shrink(index);
 break;
 };
}
void Snake::move(sf::Time dt)
{
 constexpr static auto SnakeSpeed = 80.f;
 auto vx = std::sin(radian(mDirection)) * SnakeSpeed * dt.asSeconds();
 auto vy = std::cos(radian(mDirection)) * SnakeSpeed * dt.asSeconds();
 mSnakeBody.front().move(vx, vy);
 auto begin = mSnakeBody.begin();
 auto end = mSnakeBody.end();
 for (auto current = std::next(begin); current != end; ++current)
 {
 auto previous = std::prev(current);
 auto position = previous->getPosition() - current->getPosition();
 auto angle = std::atan2(position.x, position.y);
 auto x = previous->getPosition().x - std::sin(angle) * mBlockSize;
 auto y = previous->getPosition().y - std::cos(angle) * mBlockSize;
 current->setPosition(x, y);
 };
}
void Snake::shrink(std::size_t index)
{
 --mLives;
 auto it = mSnakeBody.begin();
 std::advance(it, index);
 mSnakeBody.erase(it, mSnakeBody.end());
 mLivesText.setString("Lives: " + std::to_string(mLives));
}

World.hpp

#pragma once
#include "Snake.hpp"
#include <SFML/Window/Keyboard.hpp>
#include <SFML/Graphics/CircleShape.hpp>
#include <map>
class World : sf::NonCopyable
{
 using KeysMap = std::map<sf::Keyboard::Key, Snake::Direction>;
public:
 explicit World(sf::RenderTarget& window, const sf::Font& font);
 void update(sf::Time dt);
 void draw();
private:
 void respawnFruit();
 void handleRealTimeInput();
 void checkCollision();
 void checkGameEnd();
private:
 sf::RenderTarget& mWindow;
 Snake mSnake;
 std::size_t mScore;
 sf::RectangleShape mBorder;
 sf::CircleShape mFruit;
 KeysMap mKeyBinding;
 sf::Text mScoreText;
};

World.cpp

#include "World.hpp"
#include "Utility.hpp"
#include <SFML/Graphics/RenderWindow.hpp>
using namespace utility;
World::World(sf::RenderTarget& window, const sf::Font& font)
 : mWindow(window)
 , mSnake(font, static_cast<float>(window.getSize().x))
 , mScore()
{
 const auto blockSize = mSnake.getBlockSize();
 const auto x = window.getSize().x / 2.f;
 const auto y = window.getSize().y / 2.f + blockSize;
 const auto width = window.getSize().x - 2 * blockSize;
 const auto height = window.getSize().y - 4 * blockSize;
 mBorder.setPosition(x, y);
 mBorder.setSize({ width, height });
 mBorder.setOrigin(mBorder.getSize() / 2.f);
 mBorder.setFillColor(sf::Color::Transparent);
 mBorder.setOutlineThickness(-blockSize);
 mBorder.setOutlineColor(sf::Color(211, 211, 211));
 mFruit.setRadius(blockSize / 2.f);
 const auto originX = mFruit.getGlobalBounds().width / 2u;
 const auto originY = mFruit.getGlobalBounds().height / 2u;
 mFruit.setOrigin(originX, originY);
 mFruit.setFillColor(sf::Color::Red);
 respawnFruit();
 // initial keys binding
 mKeyBinding.emplace(sf::Keyboard::Up, Snake::Up);
 mKeyBinding.emplace(sf::Keyboard::Down, Snake::Down);
 mKeyBinding.emplace(sf::Keyboard::Left, Snake::Left);
 mKeyBinding.emplace(sf::Keyboard::Right, Snake::Right);
 mScoreText.setString("Score: " + std::to_string(mScore));
 mScoreText.setFont(font);
 mScoreText.setStyle(sf::Text::Bold);
 mScoreText.setCharacterSize(30);
 mScoreText.setColor(sf::Color::White);
 mScoreText.setPosition(30.f, 0.f);
}
void World::update(sf::Time dt)
{
 handleRealTimeInput();
 checkGameEnd();
 checkCollision();
 mSnake.update(dt);
}
void World::draw()
{
 mWindow.draw(mBorder);
 mWindow.draw(mFruit);
 mWindow.draw(mSnake);
 mWindow.draw(mScoreText);
}
void World::respawnFruit()
{
 const static auto size = mSnake.getBlockSize();
 const static auto bound = mBorder.getGlobalBounds();
 const static auto width = mFruit.getGlobalBounds().width / 2u;
 const static auto height = mFruit.getGlobalBounds().height / 2u;
 static DistType distX(bound.left + size + width, bound.width - width);
 static DistType distY(bound.top + size + height, bound.height + size + height);
 do
 mFruit.setPosition(random(distX), random(distY));
 while (mSnake.hasCollideWithFruit(mFruit));
}
void World::handleRealTimeInput()
{
 for (const auto& pair : mKeyBinding)
 {
 if (sf::Keyboard::isKeyPressed(pair.first))
 mSnake.setDirection(pair.second);
 }
}
void World::checkCollision()
{
 const static auto bound = sf::FloatRect
 {
 mBorder.getGlobalBounds().left + 2 * mSnake.getBlockSize(),
 mBorder.getGlobalBounds().top + 2 * mSnake.getBlockSize(),
 mBorder.getGlobalBounds().width - 4 * mSnake.getBlockSize(),
 mBorder.getGlobalBounds().height - 4 * mSnake.getBlockSize(),
 };
 if (!bound.intersects(mSnake.getGlobalBounds()))
 {
 mScore = 0;
 mScoreText.setString("Score: " + std::to_string(mScore));
 mSnake.lose();
 }
 if (mFruit.getGlobalBounds().intersects(mSnake.getGlobalBounds()))
 {
 mSnake.extend();
 mScore += 10;
 mScoreText.setString("Score: " + std::to_string(mScore));
 respawnFruit();
 }
}
void World::checkGameEnd()
{
 if (mSnake.hasLost())
 {
 mScore = 0;
 mSnake.reset();
 mScoreText.setString("Score: " + std::to_string(mScore));
 }
}

Game.hpp

#pragma once
#include "World.hpp"
#include <SFML/Graphics/RenderWindow.hpp>
class Game : sf::NonCopyable
{
public:
 Game();
 void run();
private:
 void processEvents();
 void update(sf::Time dt);
 void render();
private:
 sf::RenderWindow mWindow;
 World mWorld;
 sf::Font mFont;
};

Game.cpp

#include "Game.hpp"
#include <SFML/Window/Event.hpp>
Game::Game()
 : mWindow(sf::VideoMode(640, 480), "Snake")
 , mWorld(mWindow, mFont)
{
 if (!mFont.loadFromFile("Media/arial.ttf"))
 throw std::runtime_error("Can't load font file");
}
void Game::run()
{
 sf::Clock clock;
 auto timeSinceLastUpdate = sf::Time::Zero;
 const auto TimePerFrame = sf::seconds(1 / 60.f);
 while (mWindow.isOpen())
 {
 auto dt = clock.restart();
 timeSinceLastUpdate += dt;
 while (timeSinceLastUpdate > TimePerFrame)
 {
 timeSinceLastUpdate -= TimePerFrame;
 processEvents();
 update(TimePerFrame);
 }
 render();
 }
}
void Game::processEvents()
{
 sf::Event event;
 while (mWindow.pollEvent(event))
 {
 if (event.type == sf::Event::Closed)
 mWindow.close();
 }
}
void Game::update(sf::Time dt)
{
 mWorld.update(dt);
}
void Game::render()
{
 mWindow.clear();
 mWorld.draw();
 mWindow.display();
}

Main.cpp

#include "Game.hpp"
#include <stdexcept>
#include <iostream>
int main()
{
 try
 {
 Game game;
 game.run();
 }
 catch (std::runtime_error& e)
 {
 std::cout << "Exception: " << e.what() << std::endl;
 return 1;
 }
}
Mixhab
1721 silver badge7 bronze badges
asked Jan 10, 2016 at 16:29
\$\endgroup\$

0

Know someone who can answer? Share a link to this question via email, Twitter, or Facebook.

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.