Browse Source

Ver 0.1, without custom settings and graphics

master
oss 4 months ago
parent
commit
c82d22b8df
13 changed files with 746 additions and 0 deletions
  1. +4
    -0
      .gitignore
  2. +15
    -0
      CMakeLists.txt
  3. +71
    -0
      include/core.h
  4. +39
    -0
      include/field.h
  5. +25
    -0
      include/food.h
  6. +43
    -0
      include/game.h
  7. +49
    -0
      include/snake.h
  8. +124
    -0
      src/core.cpp
  9. +68
    -0
      src/field.cpp
  10. +26
    -0
      src/food.cpp
  11. +148
    -0
      src/game.cpp
  12. +30
    -0
      src/main.cpp
  13. +104
    -0
      src/snake.cpp

+ 4
- 0
.gitignore View File

@ -32,3 +32,7 @@
*.out
*.app
# Custom
*.user
*.autosave

+ 15
- 0
CMakeLists.txt View File

@ -0,0 +1,15 @@
cmake_minimum_required(VERSION 3.5)
project(snake-sfml LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -Wpedantic -Werror")
include_directories(src include)
set(SOURCES src/core.cpp src/field.cpp src/food.cpp src/game.cpp src/snake.cpp src/main.cpp)
set(HEADER_FILES include/core.h include/field.h include/food.h include/game.h include/snake.h)
add_executable(snake-sfml ${SOURCES} ${HEADER_FILES})
# add_compile_options(-Wall -Wextra -pedantic -Werror)
find_package(SFML REQUIRED graphics window system)
target_link_libraries(snake-sfml sfml-system sfml-graphics)

+ 71
- 0
include/core.h View File

@ -0,0 +1,71 @@
#ifndef CORE_H
#define CORE_H
#include <ostream>
#include <SFML/System.hpp>
const bool DEBUG(true);
const sf::Int32 CLOCK_TICK(500);
const unsigned MIN_WINDOW_SIDE_SIZE{ 800 };
const unsigned MIN_DIFFICULTY{ 1 };
const unsigned MAX_DIFFICULTY{ 10 };
const std::size_t MIN_FIELD_SIZE{ 10 };
const std::size_t MIN_SNAKE_SIZE{ 3 };
enum class Direction
{
None,
Up,
Down,
Left,
Right
};
Direction oppositeDirection(const Direction& direction);
struct Point
{
Point(int rowVal = 0, int colVal = 0) :
row(rowVal),
col(colVal)
{}
friend std::ostream& operator<<(std::ostream& os, const Point& p);
int row;
int col;
};
class Settings
{
public:
explicit Settings();
unsigned windowSideSize() const noexcept;
unsigned difficulty() const noexcept;
std::size_t fieldSize() const noexcept;
std::size_t startSnakeSize() const noexcept;
float cellSideSize() const noexcept;
Direction startSnakeDirection() const noexcept;
Point startSnakePosition() const noexcept;
bool setDifficulty(unsigned newVal);
bool setFieldSize(std::size_t newVal);
bool setStartSnakeSize(std::size_t newVal);
bool setStartSnakeDirection(Direction newVal);
bool setStartSnakePosition(Point newVal);
private:
unsigned _windowSideSize;
unsigned _difficulty;
std::size_t _fieldSize;
std::size_t _startSnakeSize;
float _cellSideSize;
Direction _startSnakeDirection;
Point _startSnakePosition;
};
#endif // CORE_H

+ 39
- 0
include/field.h View File

@ -0,0 +1,39 @@
#ifndef FIELD_H
#define FIELD_H
#include <vector>
#include <ostream>
#include <SFML/Graphics.hpp>
#include "core.h"
// No need to do full class for Cell
enum class CellType
{
Field,
Snake,
Food
};
class Field : public sf::Drawable
{
public:
explicit Field(std::size_t sideSize, float cellSideSize);
const CellType& getCellAt(const Point& point) const noexcept;
std::size_t numCells() const noexcept;
Point getDependentPoint(const Point& point) const noexcept;
Point getRandomCellPosition(const CellType& type) const;
void setCellAt(const Point& p, const CellType& newVal);
friend std::ostream& operator<<(std::ostream& os, const Field& f);
private:
virtual void draw(sf::RenderTarget& target, sf::RenderStates states) const override;
std::vector<std::vector<CellType>> _field;
sf::RectangleShape _rect;
};
#endif // FIELD_H

+ 25
- 0
include/food.h View File

@ -0,0 +1,25 @@
#ifndef FOOD_H
#define FOOD_H
#include <ostream>
#include <SFML/Graphics.hpp>
#include "core.h"
class Food : public sf::Drawable
{
public:
explicit Food(float foodSize);
void setPosition(const Point& newVal);
friend std::ostream& operator<<(std::ostream& os, const Food& f);
private:
virtual void draw(sf::RenderTarget& target, sf::RenderStates states) const override;
sf::RectangleShape _rect;
Point _position;
};
#endif // FOOD_H

+ 43
- 0
include/game.h View File

@ -0,0 +1,43 @@
#ifndef GAME_H
#define GAME_H
#include <SFML/Graphics.hpp>
#include "core.h"
#include "field.h"
#include "snake.h"
#include "food.h"
class Game
{
public:
explicit Game(const Settings& settings);
void run();
private:
void processEvents();
void update();
void render();
void handleInput(sf::Keyboard::Key key);
void placeFood();
enum class GameState
{
Running,
Victory,
Defeat
};
sf::RenderWindow _window;
Field _field;
Snake _snake;
Food _food;
sf::Int32 _tick;
GameState _gameState;
Direction _direction;
};
#endif // GAME_H

+ 49
- 0
include/snake.h View File

@ -0,0 +1,49 @@
#ifndef SNAKE_H
#define SNAKE_H
#include <list>
#include <tuple>
#include <memory>
#include <ostream>
#include <SFML/Graphics.hpp>
#include "core.h"
class Snake : public sf::Drawable
{
public:
explicit Snake(const Point& tailPosition, const Direction& direction, float partSize);
/// Returns list of Points of snake parts
std::list<Point> snakeCoords() const noexcept;
std::size_t size() const noexcept;
const Point& head() const noexcept;
const Point& tail() const noexcept;
/// Returns new position and direction for certain direction
std::tuple<Point, Direction> nextPosition(const Direction& newDirection) const noexcept;
/// Moves snake head
void moveTo(const Point& newPosition, const Direction& newDirection);
void grow();
friend std::ostream& operator<<(std::ostream& os, const Snake& s);
private:
struct SnakePart
{
explicit SnakePart(const Point& position, const Direction& direction, float partSize);
sf::RectangleShape rect;
Point position;
Direction direction;
};
using SnakePartUPtr = std::unique_ptr<SnakePart>;
virtual void draw(sf::RenderTarget& target, sf::RenderStates states) const override;
std::list<SnakePartUPtr> _body;
SnakePartUPtr _tailShadow;
};
#endif // SNAKE_H

+ 124
- 0
src/core.cpp View File

@ -0,0 +1,124 @@
#include "core.h"
Direction oppositeDirection(const Direction& direction)
{
switch (direction) {
case Direction::Down:
return Direction::Up;
break;
case Direction::Up:
return Direction::Down;
break;
case Direction::Left:
return Direction::Right;
break;
case Direction::Right:
return Direction::Left;
break;
default:
return Direction::None;
break;
}
}
std::ostream& operator<<(std::ostream& os, const Point& p)
{
os << "(" << p.row << ", " << p.col << ")";
return os;
}
Settings::Settings() :
_windowSideSize(MIN_WINDOW_SIDE_SIZE),
_difficulty(MIN_DIFFICULTY),
_fieldSize(MIN_FIELD_SIZE),
_startSnakeSize(MIN_SNAKE_SIZE),
_cellSideSize(static_cast<float>(_windowSideSize) / _fieldSize),
_startSnakeDirection(Direction::Left),
_startSnakePosition(_fieldSize / 2 - 1, _fieldSize / 2 - 1)
{
}
unsigned Settings::windowSideSize() const noexcept
{
return _windowSideSize;
}
unsigned Settings::difficulty() const noexcept
{
return _difficulty;
}
std::size_t Settings::fieldSize() const noexcept
{
return _fieldSize;
}
std::size_t Settings::startSnakeSize() const noexcept
{
return _startSnakeSize;
}
float Settings::cellSideSize() const noexcept
{
return _cellSideSize;
}
Direction Settings::startSnakeDirection() const noexcept
{
return _startSnakeDirection;
}
Point Settings::startSnakePosition() const noexcept
{
return _startSnakePosition;
}
bool Settings::setDifficulty(unsigned newVal)
{
if (newVal < MIN_DIFFICULTY || newVal > MAX_DIFFICULTY)
return false;
_difficulty = newVal;
return true;
}
bool Settings::setFieldSize(std::size_t newVal)
{
if (newVal < MIN_FIELD_SIZE)
return false;
_fieldSize = newVal;
_cellSideSize = static_cast<float>(_windowSideSize) / _fieldSize;
return true;
}
bool Settings::setStartSnakeDirection(Direction newVal)
{
if (newVal == Direction::None)
return false;
_startSnakeDirection = newVal;
return true;
}
bool Settings::setStartSnakeSize(std::size_t newVal)
{
if (newVal < MIN_SNAKE_SIZE || newVal >= _fieldSize)
return false;
_startSnakeSize = newVal;
return true;
}
bool Settings::setStartSnakePosition(Point newVal)
{
if (newVal.row >= static_cast<int>(_fieldSize) || newVal.col >= static_cast<int>(_fieldSize))
return false;
_startSnakePosition = newVal;
return true;
}

+ 68
- 0
src/field.cpp View File

@ -0,0 +1,68 @@
#include <iostream>
#include "field.h"
Field::Field(std::size_t sideSize, float cellSideSize) :
_field(sideSize, std::vector<CellType>(sideSize, CellType::Field)),
_rect(sf::Vector2f(cellSideSize * sideSize, cellSideSize * sideSize))
{
_rect.setFillColor(sf::Color::Green);
}
const CellType& Field::getCellAt(const Point& point) const noexcept
{
return _field[point.row][point.col];
}
std::size_t Field::numCells() const noexcept
{
return _field.size() * _field.back().size();
}
Point Field::getDependentPoint(const Point& point) const noexcept
{
int numRows{ static_cast<int>(_field.size()) };
int numCols{ static_cast<int>(_field.back().size()) };
return {
((point.row >= 0) ? (point.row % numRows) : (numRows + (point.row % numRows))),
((point.col >= 0) ? (point.col % numCols) : (numCols + (point.col % numCols)))
};
}
Point Field::getRandomCellPosition(const CellType &type) const
{
// Bad algorithm, maybe need to track free fields
Point res;
do
{
res.row = rand() % _field.size();
res.col = rand() % _field.back().size();
} while (_field[res.row][res.col] != type);
return res;
}
void Field::setCellAt(const Point& point, const CellType& newVal)
{
_field[point.row][point.col] = newVal;
}
std::ostream& operator<<(std::ostream& os, const Field& f)
{
for (const auto& row : f._field)
{
for (const auto& col : row)
{
os << ((col == CellType::Field) ? "* " : (col == CellType::Snake) ? "S " : "F ");
}
os << std::endl;
}
return os;
}
void Field::draw(sf::RenderTarget& target, sf::RenderStates states) const
{
target.draw(_rect, states);
}

+ 26
- 0
src/food.cpp View File

@ -0,0 +1,26 @@
#include <iostream>
#include "food.h"
Food::Food(float foodSize) :
_rect(sf::Vector2f(foodSize, foodSize))
{
_rect.setFillColor(sf::Color::Red);
}
void Food::setPosition(const Point &newVal)
{
_position = newVal;
_rect.setPosition(_position.col * _rect.getSize().x, _position.row * _rect.getSize().y);
}
std::ostream& operator<<(std::ostream& os, const Food& f)
{
os << f._position;
return os;
}
void Food::draw(sf::RenderTarget& target, sf::RenderStates states) const
{
target.draw(_rect, states);
}

+ 148
- 0
src/game.cpp View File

@ -0,0 +1,148 @@
#include <iostream>
#include "game.h"
Game::Game(const Settings& settings) :
_window(sf::VideoMode(settings.windowSideSize(), settings.windowSideSize()), "SFML App"),
_field(settings.fieldSize(), settings.cellSideSize()),
_snake(settings.startSnakePosition(), settings.startSnakeDirection(), settings.cellSideSize()),
_food(settings.cellSideSize()),
_tick(CLOCK_TICK - ((settings.difficulty()) - 1) * (CLOCK_TICK / MAX_DIFFICULTY)),
_gameState(GameState::Running),
_direction(Direction::None)
{
for (std::size_t i = 0; i < settings.startSnakeSize(); ++i)
{
_snake.grow();
auto [newPosition, newDirection] = _snake.nextPosition(settings.startSnakeDirection());
newPosition = _field.getDependentPoint(newPosition);
_snake.moveTo(newPosition, newDirection);
_field.setCellAt(_snake.head(), CellType::Snake);
}
placeFood();
}
void Game::run()
{
sf::Clock clock;
while (_window.isOpen() && _gameState == GameState::Running)
{
processEvents();
sf::Time elapsed = clock.getElapsedTime();
if (elapsed.asMilliseconds() >= _tick)
{
update();
clock.restart();
}
render();
}
}
void Game::processEvents()
{
sf::Event event;
while (_window.pollEvent(event))
{
switch (event.type)
{
case sf::Event::KeyPressed:
handleInput(event.key.code);
break;
case sf::Event::Closed:
_window.close();
break;
default:
break;
}
}
}
void Game::update()
{
if (DEBUG)
{
std::cout << "==================================" << std::endl
<< "FIELD." << std::endl << _field
<< "SNAKE." << std::endl << _snake << std::endl
<< "FOOD." << std::endl << _food << std::endl
<< "==================================" << std::endl << std::endl;
}
_field.setCellAt(_snake.tail(), CellType::Field);
auto [newPosition, newDirection] = _snake.nextPosition(_direction);
newPosition = _field.getDependentPoint(newPosition);
CellType destinationCell = _field.getCellAt(newPosition);
_snake.moveTo(newPosition, newDirection);
_field.setCellAt(_snake.head(), CellType::Snake);
switch (destinationCell)
{
case CellType::Snake:
_gameState = GameState::Defeat;
break;
case CellType::Food:
_snake.grow();
_field.setCellAt(_snake.tail(), CellType::Snake);
if (_snake.size() == _field.numCells())
_gameState = GameState::Victory;
else
placeFood();
break;
default:
break;
}
}
void Game::render()
{
_window.clear();
_window.draw(_field);
_window.draw(_snake);
_window.draw(_food);
_window.display();
}
void Game::handleInput(sf::Keyboard::Key key)
{
switch (key)
{
case sf::Keyboard::W:
case sf::Keyboard::Up:
case sf::Keyboard::K:
_direction = Direction::Up;
break;
case sf::Keyboard::S:
case sf::Keyboard::Down:
case sf::Keyboard::J:
_direction = Direction::Down;
break;
case sf::Keyboard::A:
case sf::Keyboard::Left:
case sf::Keyboard::H:
_direction = Direction::Left;
break;
case sf::Keyboard::D:
case sf::Keyboard::Right:
case sf::Keyboard::L:
_direction = Direction::Right;
break;
default:
break;
}
}
void Game::placeFood()
{
Point position = _field.getRandomCellPosition(CellType::Field);
_food.setPosition(position);
_field.setCellAt(position, CellType::Food);
}

+ 30
- 0
src/main.cpp View File

@ -0,0 +1,30 @@
#include <iostream>
#include <cstdlib>
#include <ctime>
#include "core.h"
#include "game.h"
int main()
{
std::srand(static_cast<unsigned>(std::time(nullptr)));
Settings settings;
if (DEBUG)
{
std::cout << "Windows side size: " << settings.windowSideSize() << std::endl
<< "Field size: " << settings.fieldSize() << std::endl
<< "Difficulty: " << settings.difficulty() << std::endl
<< "Start snake size: " << settings.startSnakeSize() << std::endl
<< "Cell side size: " << settings.cellSideSize() << std::endl
<< "Start snake direction: " << static_cast<int>(settings.startSnakeDirection()) << std::endl
<< "Start snake position: " << settings.startSnakePosition() << std::endl;
}
settings.setDifficulty(MAX_DIFFICULTY);
Game game(settings);
game.run();
return 0;
}

+ 104
- 0
src/snake.cpp View File

@ -0,0 +1,104 @@
#include "snake.h"
Snake::SnakePart::SnakePart(const Point& position, const Direction& direction, float partSize) :
rect(sf::Vector2f(partSize, partSize)),
position(position),
direction(direction)
{
rect.setFillColor(sf::Color::Yellow);
}
Snake::Snake(const Point& tailPosition, const Direction& direction, float partSize) :
_body(0),
_tailShadow(std::make_unique<SnakePart>(tailPosition, direction, partSize))
{
}
std::list<Point> Snake::snakeCoords() const noexcept
{
std::list<Point> res;
for (const auto& part : _body)
res.emplace_back(part->position);
return res;
}
std::size_t Snake::size() const noexcept
{
return _body.size();
}
const Point& Snake::head() const noexcept
{
return _body.front()->position;
}
const Point& Snake::tail() const noexcept
{
return _body.back()->position;
}
std::tuple<Point, Direction> Snake::nextPosition(const Direction& newDirection) const noexcept
{
Point resultPosition = _body.front()->position;
Direction resultDirection = _body.front()->direction;
if (newDirection != Direction::None && newDirection != oppositeDirection(resultDirection))
resultDirection = newDirection;
switch (resultDirection) {
case Direction::Up:
--resultPosition.row;
break;
case Direction::Down:
++resultPosition.row;
break;
case Direction::Left:
--resultPosition.col;
break;
case Direction::Right:
++resultPosition.col;
break;
default:
break;
}
return { resultPosition, resultDirection };
}
void Snake::moveTo(const Point& newPosition, const Direction& newDirection)
{
SnakePartUPtr newHead = std::move(_body.back());
_tailShadow->direction = newHead->direction;
_tailShadow->position = newHead->position;
_body.pop_back();
newHead->position = newPosition;
newHead->direction = newDirection;
newHead->rect.setPosition(newHead->position.col * newHead->rect.getSize().x, newHead->position.row * newHead->rect.getSize().y);
_body.emplace_front(std::move(newHead));
}
void Snake::grow()
{
_tailShadow->rect.setPosition(_tailShadow->position.col * _tailShadow->rect.getSize().x, _tailShadow->position.row * _tailShadow->rect.getSize().y);
_body.emplace_back(std::make_unique<SnakePart>(*_tailShadow));
}
std::ostream& operator<<(std::ostream& os, const Snake& s)
{
os << "HEAD ";
for (const auto& part : s._body)
os << part->position << " ";
os << "TAIL";
return os;
}
void Snake::draw(sf::RenderTarget& target, sf::RenderStates states) const
{
for (const auto& part : _body)
target.draw(part->rect, states);
}

Loading…
Cancel
Save