I started learning programming about a year ago, starting with Python. Half a year ago, I moved onto C++ and this is my first large project with that language. Have I understood the basics of the language?
In Main.cpp:
#pragma warning(disable : 4996)
#pragma warning(disable : 4244)
#include <memory>
#include <stdlib.h>
#include <stdio.h>
#include <time.h>
#include <SFML\Graphics.hpp>
#include "Snake.h"
#include "SnakeFood.h"
#include "HighScoreFile.h"
class Snake;
class SnakeFood;
class HighScoreFile;
void displayScores(sf::RenderWindow& window, HighScoreFile& highScorefile, int score, const sf::Font& font);
void displayNewBest(sf::RenderWindow& window, const sf::Font& font);
bool playAgain(sf::RenderWindow& window);
std::unique_ptr<sf::Font> newFont(std::string&& fileName);
std::unique_ptr<sf::SoundBuffer> newSoundBuffer(std::string&& fileName);
int main()
{
srand(time(NULL));
static auto scoredSoundBuffer = newSoundBuffer("Sound Effects\\Scored.wav");
static auto celebrationSoundbuffer = newSoundBuffer("Sound Effects\\Celebration.wav");
static auto defeatSoundBuffer = newSoundBuffer("Sound Effects\\Defeat.wav");
static auto startupSoundBuffer = newSoundBuffer("Sound Effects\\Startup.wav");
sf::Sound scoredSoundEffect{ *scoredSoundBuffer };
sf::Sound celebrationSoundEffect{ *celebrationSoundbuffer };
sf::Sound defeatSoundEffect{ *defeatSoundBuffer };
sf::Sound startupSoundEffect{ *startupSoundBuffer };
scoredSoundEffect.setVolume(30.f);
celebrationSoundEffect.setVolume(30.f);
defeatSoundEffect.setVolume(30.f);
startupSoundEffect.setVolume(30.f);
static auto gameTextFont = newFont("Arcade Classic.ttf");
sf::RenderWindow window(sf::VideoMode(665, 595), "Snake", sf::Style::Close | sf::Style::Titlebar);
while (true)
{
Snake snake{};
SnakeFood food{ window, snake };
int score{ 0 };
HighScoreFile highScoreFile{ "high-score-file.txt" };
startupSoundEffect.play();
while (window.isOpen())
{
sf::Event event;
while (window.pollEvent(event))
{
switch (event.type)
{
case sf::Event::KeyPressed:
switch (event.key.code)
{
case sf::Keyboard::W:
case sf::Keyboard::Up:
snake.changeDirection(Direction::Up);
break;
case sf::Keyboard::S:
case sf::Keyboard::Down:
snake.changeDirection(Direction::Down);
break;
case sf::Keyboard::A:
case sf::Keyboard::Left:
snake.changeDirection(Direction::Left);
break;
case sf::Keyboard::D:
case sf::Keyboard::Right:
snake.changeDirection(Direction::Right);
break;
}
break;
case sf::Event::Closed:
exit(0);
break;
default:
//No need to handle unrecognised events
break;
}
}
snake.checkIfOutOfBounds(window);
snake.move();
if (snake.isTouchingFood(food))
{
scoredSoundEffect.play();
snake.grow();
score++;
food.setToRandomPosition(window, snake);
}
window.clear(sf::Color::Black);
snake.drawBody(window);
window.draw(food);
sf::Text scoreText{ std::to_string(score), *gameTextFont, 30 };
scoreText.setPosition(10.f, 5.f);
window.draw(scoreText);;
window.display();
if (snake.isTouchingSelf())
{
if (score > highScoreFile.getHighScore())
{
celebrationSoundEffect.play();
displayNewBest(window, *gameTextFont);
highScoreFile.editHighScore(score);
_sleep(1500);
}
else
{
defeatSoundEffect.play();
}
_sleep(1000);
displayScores(window, highScoreFile, score, *gameTextFont);
if (!playAgain(window))
{
exit(0);
}
break;
}
}
}
return 0;
}
void displayScores(sf::RenderWindow& window, HighScoreFile& highScoreFile, int score, const sf::Font& font)
{
window.clear(sf::Color::Black);
sf::Text scoreText{ "SCORE: " + std::to_string(score), font, 90 };
if (score < 10)
{
scoreText.setPosition(85.f, 85.f);
}
else
{
scoreText.setPosition(55.f, 85.f);
}
//scoreText.setPosition(85.f, 85.f);
scoreText.setFillColor(sf::Color::Green);
sf::Text highScoreText{ "HI SCORE: " + std::to_string(highScoreFile.getHighScore()), font, 80 };
highScoreText.setFillColor(sf::Color::Green);
if (highScoreFile.getHighScore() < 10)
{
highScoreText.setPosition(40.f, 375.f);
}
else
{
highScoreText.setPosition(10.f, 375.f);
}
//highScoreText.setPosition(40.f, 375.f);
window.draw(scoreText);
window.draw(highScoreText);
window.display();
}
void displayNewBest(sf::RenderWindow& window, const sf::Font& font)
{
sf::Text newBest{ "NEW BEST!", font, 75 };
newBest.setPosition(110.f, 250.f);
newBest.setFillColor(sf::Color::Red);
window.draw(newBest);
window.display();
}
bool playAgain(sf::RenderWindow& window)
{
while (true)
{
sf::Event event;
while (window.pollEvent(event))
{
if (event.type == sf::Event::Closed)
{
window.close();
}
switch (event.key.code)
{
case sf::Keyboard::Q:
return false;
break;
case sf::Keyboard::Z:
return true;
break;
default:
//No need to handle unrecognised events
break;
}
}
}
}
std::unique_ptr<sf::Font> newFont(std::string&& fileName)
{
auto font = std::make_unique<sf::Font>();
if (!font->loadFromFile(fileName))
{
exit(0);
}
return font;
}
std::unique_ptr<sf::SoundBuffer> newSoundBuffer(std::string&& fileName)
{
auto buffer = std::make_unique<sf::SoundBuffer>();
if (!buffer->loadFromFile(fileName))
{
exit(0);
}
return buffer;
}
In SnakeRect.h:
#pragma once
#include <SFML\Graphics.hpp>
enum class Direction { Left, Right, Up, Down };
class SnakeRect : public sf::RectangleShape
{
using RectangleShape::RectangleShape;
public:
SnakeRect(Direction dir);
Direction direction() const;
Direction oppositeDirection() const;
private:
Direction direction_;
};
In SnakeRect.cpp:
#include "SnakeRect.h"
SnakeRect::SnakeRect(Direction dir) : RectangleShape{}, direction_{ dir }
{
}
Direction SnakeRect::direction()
const {
return direction_;
}
Direction SnakeRect::oppositeDirection()
const {
switch (direction_)
{
case Direction::Up:
return Direction::Down;
break;
case Direction::Down:
return Direction::Up;
break;
case Direction::Right:
return Direction::Left;
break;
case Direction::Left:
return Direction::Right;
break;
default:
break;
}
}
In Snake.h:
#pragma once
#include <vector>
#include <SFML/Audio.hpp>
#include "SnakeRect.h"
#include "SnakeFood.h"
class Snake
{
public:
Snake();
Snake(sf::Vector2f startingPos, Direction startingDir);
bool isTouchingFood(const SnakeFood& food);
bool isTouchingSelf();
void move();
void changeDirection(Direction dir);
void checkIfOutOfBounds(const sf::RenderWindow& window);
void grow();
void drawBody(sf::RenderWindow& window);
friend class SnakeFood;
private:
std::vector<SnakeRect> body_;
static const float thickness;
static const float speed;
static const sf::Color color;
static const float startingLength;
static const sf::Vector2f defaultStartingPos;
static const Direction defaultStartingDir;
};
In Snake.cpp:
#pragma warning(disable : 4996)
#include <chrono>
#include "Snake.h"
const float Snake::thickness{ 35.f };
const float Snake::speed{ 35.f };
const sf::Color Snake::color{ sf::Color::Green };
const float Snake::startingLength{ 3.f };
const sf::Vector2f Snake::defaultStartingPos{280.f, 280.f};
const Direction Snake::defaultStartingDir{Direction::Right};
Snake::Snake() : Snake{defaultStartingPos, defaultStartingDir}
{
}
Snake::Snake(sf::Vector2f startingPos, Direction startingDir)
{
SnakeRect newRect{ startingDir };
newRect.setSize(sf::Vector2f(startingLength*speed, (float)thickness));
newRect.setPosition(startingPos);
newRect.setFillColor(color);
body_.push_back(newRect);
}
bool Snake::isTouchingFood(const SnakeFood& food)
{
const SnakeRect& frontRect{ (body_.at(body_.size() - 1)) };
return (frontRect.getGlobalBounds().intersects(food.getGlobalBounds()));
}
bool Snake::isTouchingSelf()
{
SnakeRect& frontRect{ body_.at(body_.size() - 1) };
for (auto it = body_.begin(); it != std::prev(body_.end()); it++)
{
if (frontRect.getGlobalBounds().intersects(it->getGlobalBounds()))
{
return true;
}
}
return false;
}
void Snake::move()
{
SnakeRect& backRect{ body_.at(0) };
SnakeRect& frontRect{ body_.at(body_.size() - 1) };
for (int i{ 0 }; i < 2; i++) {
SnakeRect& currentRect{ (i == 0) ? backRect : frontRect };
float modifier{ (i == 0) ? -(float)speed : (float)speed };
switch (currentRect.direction())
{
case Direction::Up:
currentRect.setSize(sf::Vector2f(currentRect.getSize().x, (currentRect.getSize().y) + modifier));
currentRect.move(0, (i == 1) ? -modifier : 0);
break;
case Direction::Down:
currentRect.setSize(sf::Vector2f(currentRect.getSize().x, (currentRect.getSize().y) + modifier));
currentRect.move(0, (i == 0) ? fabs(modifier) : 0);
break;
case Direction::Left:
currentRect.setSize(sf::Vector2f((currentRect.getSize().x) + modifier, currentRect.getSize().y));
currentRect.move((i == 1) ? -modifier : 0, 0);
break;
case Direction::Right:
currentRect.setSize(sf::Vector2f((currentRect.getSize().x) + modifier, currentRect.getSize().y));
currentRect.move((i == 0) ? fabs(modifier) : 0, 0);
break;
default:
//Will never execute since Direction is an enum
break;
}
}
if (backRect.getSize().x <= 0 || backRect.getSize().y <= 0)
{
body_.erase(body_.begin() + 0);
}
_sleep(150);
}
void Snake::changeDirection(Direction dir)
{
SnakeRect frontRect{ body_.at(body_.size() - 1) };
float frontRectX{ frontRect.getPosition().x };
float frontRectY{ frontRect.getPosition().y };
if (dir != frontRect.direction() && dir != frontRect.oppositeDirection())
{
float xPosition{};
float yPosition{};
switch (frontRect.direction()) //Can shorten this down, will look into it
{
case Direction::Up:
xPosition = (dir == Direction::Left ? frontRectX : frontRectX + (float)thickness);
yPosition = frontRectY;
break;
case Direction::Down:
xPosition = (dir == Direction::Left ? frontRectX : frontRectX + float(thickness));
yPosition = frontRectY + frontRect.getSize().y - (float)thickness;
break;
case Direction::Right:
xPosition = frontRectX + frontRect.getSize().x - (float)thickness;
yPosition = (dir == Direction::Up ? frontRectY : frontRectY + (float)thickness);
break;
case Direction::Left:
xPosition = frontRectX;
yPosition = (dir == Direction::Up ? frontRectY : frontRectY + (float)thickness);
break;
default:
break;
//Will never execute
}
float xSize{ (dir == Direction::Up || dir == Direction::Down) ? (float)thickness : 0.f };
float ySize{ (dir == Direction::Up || dir == Direction::Down) ? 0.f : (float)thickness };
SnakeRect newRect{dir};
newRect.setSize(sf::Vector2f(xSize, ySize));
newRect.setPosition(xPosition, yPosition);
newRect.setFillColor(sf::Color::Green);
body_.push_back(newRect);
}
}
void Snake::checkIfOutOfBounds(const sf::RenderWindow& window)
{
const SnakeRect& frontRect{ body_.at(body_.size() - 1) };
float xPositionWithSize{ frontRect.getPosition().x + frontRect.getSize().x };
float yPositionWithSize{ frontRect.getPosition().y + frontRect.getSize().y };
bool isLeft{ frontRect.direction() == Direction::Left };
bool isRight{ frontRect.direction() == Direction::Right };
bool isUp{ frontRect.direction() == Direction::Up };
bool isDown{ frontRect.direction() == Direction::Down };
bool xOutOfBounds{ (frontRect.getPosition().x - (isLeft ? (float)speed : 0.f)) < 0 || xPositionWithSize + (isRight ? (float)speed : 0.f) > window.getSize().x };
bool yOutOfBounds{ (frontRect.getPosition().y - (isUp ? (float)speed : 0.f)) < 0 || yPositionWithSize + (isDown ? (float)speed : 0.f) > window.getSize().y };
if (xOutOfBounds || yOutOfBounds)
{
SnakeRect newRect{frontRect.direction()};
newRect.setFillColor(sf::Color::Green);
sf::Vector2f newRectSize{};
sf::Vector2f newRectPos{};
switch (frontRect.direction()) {
case Direction::Up:
newRectSize = sf::Vector2f((float)thickness, 0.f);
newRectPos = sf::Vector2f(frontRect.getPosition().x, (float)window.getSize().y);
break;
case Direction::Down:
newRectSize = sf::Vector2f((float)thickness, 0.f);
newRectPos = sf::Vector2f(frontRect.getPosition().x, 0.f);
break;
case Direction::Right:
newRectSize = sf::Vector2f(0.f, (float)thickness);
newRectPos = sf::Vector2f(0.f, frontRect.getPosition().y);
break;
case Direction::Left:
newRectSize = sf::Vector2f(0.f, (float)thickness);
newRectPos = sf::Vector2f((float)window.getSize().x, frontRect.getPosition().y);
break;
default:
break;
}
newRect.setSize(newRectSize);
newRect.setPosition(newRectPos);
body_.push_back(newRect);
}
}
void Snake::grow()
{
SnakeRect& backRect{ body_.at(0) };
switch (backRect.direction())
{
case Direction::Up:
backRect.setSize(sf::Vector2f(backRect.getSize().x, (backRect.getSize().y) + (float)speed));
break;
case Direction::Down:
backRect.setSize(sf::Vector2f(backRect.getSize().x, (backRect.getSize().y) + (float)speed));
backRect.move(0, -(float)speed);
break;
case Direction::Left:
backRect.setSize(sf::Vector2f((backRect.getSize().x) + (float)speed, backRect.getSize().y));
break;
case Direction::Right:
backRect.setSize(sf::Vector2f((backRect.getSize().x) + (float)speed, backRect.getSize().y));
backRect.move(-(float)speed, 0);
break;
default:
//Will never execute since Direction is an enum
break;
}
}
void Snake::drawBody(sf::RenderWindow& window)
{
for (const SnakeRect& rect : body_)
{
window.draw(rect);
}
}
In SnakeFood.h:
#pragma once
#include <SFML\Graphics.hpp>
class Snake;
class SnakeFood : public sf::RectangleShape {
using RectangleShape::RectangleShape;
public:
SnakeFood(const sf::RenderWindow& window, const Snake& snake);
bool isTouching(const Snake& snake);
void setToRandomPosition(const sf::RenderWindow& window, const Snake& s);
};
In SnakeFood.cpp:
#include "SnakeFood.h"
#include "Snake.h"
#include <stdlib.h>
#include <stdio.h>
#include <time.h>
SnakeFood::SnakeFood(const sf::RenderWindow& window, const Snake& snake) : RectangleShape{}
{
setSize(sf::Vector2f(15.f, 15.f));
setFillColor(sf::Color::Red);
setToRandomPosition(window, snake);
}
void SnakeFood::setToRandomPosition(const sf::RenderWindow& window, const Snake& snake)
{
do
{
float xPosition, yPosition;
xPosition = float(rand() % (window.getSize().x - int(getSize().x)));
yPosition = float(rand() % (window.getSize().y - int(getSize().y)));
setPosition(xPosition, yPosition);
} while (isTouching(snake));
}
bool SnakeFood::isTouching(const Snake& s)
{
for (const SnakeRect& rect : s.body_)
{
if (rect.getGlobalBounds().intersects(this->getGlobalBounds()))
{
return true;
}
}
return false;
}
In HighScoreFile.h:
#pragma once
#include <fstream>
class HighScoreFile
{
public:
HighScoreFile(std::string fileName);
int getHighScore();
void editHighScore(int score);
private:
const std::string highScoreFileName_;
std::fstream highScoreFile_;
};
In HighScoreFile.cpp:
#include "HighScoreFile.h"
HighScoreFile::HighScoreFile(const std::string fileName)
: highScoreFileName_{fileName}
{
}
int HighScoreFile::getHighScore()
{
highScoreFile_.open(highScoreFileName_, std::ios::in);
if (!highScoreFile_){ exit(0); }
int highScore{};
highScoreFile_ >> highScore;
highScoreFile_.close();
return highScore;
}
void HighScoreFile::editHighScore(int score)
{
highScoreFile_.open(highScoreFileName_, std::ios::out, std::ios::trunc);
if (!highScoreFile_) { exit(0); }
highScoreFile_ << score;
highScoreFile_.close();
}
```
std::thread::sleep
; fixing warnings is always better than disabling them. It also makes the code unportable, not all c++ compilers use the same warning numbers. \$\endgroup\$