diff --git a/Makefile b/Makefile index 75c1ce8..16244f8 100644 --- a/Makefile +++ b/Makefile @@ -2,17 +2,20 @@ INC := -I include STD := -std=c++11 SFML := -lsfml-graphics -lsfml-window -lsfml-system -all: dirs compile link +all: compile link -dirs: - mkdir bin build +fresh: dirs compile link compile: g++ $(INC) $(STD) -c -o build/main.o src/main.cpp + g++ $(INC) $(STD) -c -o build/Common.o src/Common.cpp g++ $(INC) $(STD) -c -o build/GameState.o src/GameState.cpp g++ $(INC) $(STD) -c -o build/Snake.o src/Snake.cpp g++ $(INC) $(STD) -c -o build/SnakeFood.o src/SnakeFood.cpp +dirs: + mkdir bin build + link: g++ build/*.o -o bin/SnakePlusPlus $(SFML) diff --git a/include/Common.h b/include/Common.h new file mode 100644 index 0000000..22c2353 --- /dev/null +++ b/include/Common.h @@ -0,0 +1,18 @@ +#ifndef COMMON_H +#define COMMON_H + +#include + +const int kGridSize = 25; + +enum PlayerDirection +{ + kLeft = 1, + kUp = 2, + kDown = 3, + kRight = 4 +}; + +bool GlobalCollision(sf::Vector2f object1Position, sf::Vector2f object2Position); + +#endif \ No newline at end of file diff --git a/include/GameState.h b/include/GameState.h index f5ccb13..adca85d 100644 --- a/include/GameState.h +++ b/include/GameState.h @@ -1,22 +1,29 @@ // GameState.h #ifndef GAMESTATE_H #define GAMESTATE_H + #include +#include "Snake.h" class GameState { -private: public: - sf::VideoMode gameVideoMode; - sf::RenderWindow gameWindow; GameState(); GameState(int newHorizontal, int newVertical); - void startNewGame(); - /* - gameGridHorizontal = (videoSizeHorizontal // 25) * 25; - gameGridVertical = (videoSizeVertical // 25) * 25; - */ - // sf::Vector2f GetGameBoundaries(); + void StartGame(void); + sf::Vector2f GetGameBoundaries(void); +protected: + ; +private: + sf::RenderWindow gameWindow; + sf::VideoMode gameVideoSettings; + SnakeFood playerFood; + Snake player; + sf::Time delay; + void GetKeyboardInput(void); + void RegenerateFood(void); + void RunGameLoop(void); + void RenderWindow(void); }; #endif diff --git a/include/Snake.h b/include/Snake.h index aa873b3..63f2edf 100644 --- a/include/Snake.h +++ b/include/Snake.h @@ -6,24 +6,25 @@ #include #include "SnakeFood.h" -sf::Vector2f CalculateNewPosition(int direction, sf::Vector2f position); -bool GlobalCollision(sf::Vector2f object1Position, sf::Vector2f object2Position); class Snake { +public: + Snake(void); + Snake(sf::Vector2f head); + void DisplaySnake(sf::RenderWindow& window); + sf::RectangleShape GetSnakeHead(void); + sf::Vector2f GetSnakeHeadPosition(void); + bool IsTouchingObject(sf::RectangleShape object); + void MoveSnake(SnakeFood* playerFood); + void UpdateDirection(int newDirection); +protected: + ; private: std::deque snakeBody; int snakeDirection = 0; -public: - Snake(); - Snake(sf::Vector2f head); - sf::Vector2f GetSnakeHeadPosition(); - sf::RectangleShape GetSnakeHead(); - void DisplaySnake(sf::RenderWindow& window); - void MoveSnake(SnakeFood& playerFood, sf::VideoMode gameVideoMode); - void SnakeFoodCollision(SnakeFood& snakeFood, sf::VideoMode gameVideoMode); - void CheckDirection(); - bool CheckBoundaries(); + sf::Vector2f CalculateNewPosition(sf::Vector2f position); + bool CheckBoundaries(void); bool IsSelfCollision(sf::RectangleShape testRectangle); }; diff --git a/include/SnakeFood.h b/include/SnakeFood.h index 67f603a..06b21cb 100644 --- a/include/SnakeFood.h +++ b/include/SnakeFood.h @@ -1,18 +1,23 @@ // SnakeFood.h #ifndef SNAKEFOOD_H #define SNAKEFOOD_H -#include + #include +#include class SnakeFood { -private: public: - sf::RectangleShape snakeFoodObject; - std::default_random_engine generator; SnakeFood(); SnakeFood(sf::Vector2f snakeFoodSize); - void GenerateNewLocation(int horizontalLocation, int verticalLocation); + void GenerateNewFood(sf::Vector2f windowSize); + sf::RectangleShape GetFoodObject(void); + sf::Vector2f GetFoodObjectPosition(void); +protected: + ; +private: + sf::RectangleShape snakeFoodObject; + std::default_random_engine generator; int GenerateRandomNumber(int generationLimit); }; diff --git a/src/Common.cpp b/src/Common.cpp new file mode 100644 index 0000000..e8887a6 --- /dev/null +++ b/src/Common.cpp @@ -0,0 +1,11 @@ +#include "Common.h" + +// Test for collision between two object positions +bool GlobalCollision(sf::Vector2f object1Position, sf::Vector2f object2Position) +{ + if (object1Position.x != object2Position.x) + return false; + if (object1Position.y != object2Position.y) + return false; + return true; +} \ No newline at end of file diff --git a/src/GameState.cpp b/src/GameState.cpp index f9a89a1..13b05f4 100644 --- a/src/GameState.cpp +++ b/src/GameState.cpp @@ -1,47 +1,90 @@ // GameState.cpp #include #include +#include "Common.h" #include "Snake.h" #include "GameState.h" GameState::GameState() { - sf::VideoMode tempVideoMode(1024, 725); - gameVideoMode = tempVideoMode; - sf::RenderWindow gameWindow(gameVideoMode, "SnakePlusPlus"); + delay = sf::milliseconds(75); + gameVideoSettings = sf::VideoMode(1025, 725); + gameWindow.create(gameVideoSettings, "SnakePlusPlus"); return; } -GameState::GameState(int newHorizontal, int newVertical) +GameState::GameState(int maxHorizontal, int maxVertical) { - sf::VideoMode tempVideoMode(newHorizontal, newVertical); - gameVideoMode = tempVideoMode; - sf::RenderWindow tempWindow(gameVideoMode, "SnakePlusPlus"); + delay = sf::milliseconds(75); + gameVideoSettings = sf::VideoMode(maxHorizontal, maxVertical); + gameWindow.create(gameVideoSettings, "SnakePlusPlus"); return; } -void GameState::startNewGame() +void GameState::StartGame() { - gameWindow.create(gameVideoMode, "SnakePlusPlus"); - sf::Time delay = sf::milliseconds(100); - int snakeDirection = 0; - Snake player(sf::Vector2f(25,25)); - SnakeFood playerFood(sf::Vector2f(25,25)); + gameWindow.create(gameVideoSettings, "SnakePlusPlus"); + Snake player(sf::Vector2f(kGridSize,kGridSize)); + SnakeFood playerFood(sf::Vector2f(kGridSize,kGridSize)); + RunGameLoop(); + return; +} +sf::Vector2f GameState::GetGameBoundaries(void) +{ + sf::Vector2f boundaries; + boundaries.x = gameVideoSettings.width; + boundaries.y = gameVideoSettings.height; + return boundaries; +} + +void GameState::GetKeyboardInput(void) +{ + sf::Event event; + while (gameWindow.pollEvent(event)) + { + if ((event.type == sf::Event::Closed) || + (sf::Keyboard::isKeyPressed(sf::Keyboard::Escape))) + gameWindow.close(); + } + if (sf::Keyboard::isKeyPressed(sf::Keyboard::Left)) + player.UpdateDirection(kLeft); + if (sf::Keyboard::isKeyPressed(sf::Keyboard::Up)) + player.UpdateDirection(kUp); + if (sf::Keyboard::isKeyPressed(sf::Keyboard::Down)) + player.UpdateDirection(kDown); + if (sf::Keyboard::isKeyPressed(sf::Keyboard::Right)) + player.UpdateDirection(kRight); + return; +} + +// Generates new food until not colliding with player +void GameState::RegenerateFood(void) +{ + // Keep making new food until generating a valid spot + while (player.IsTouchingObject(playerFood.GetFoodObject())) + playerFood.GenerateNewFood(GetGameBoundaries()); + return; +} + +void GameState::RunGameLoop(void) +{ while (gameWindow.isOpen()) { - sf::Event event; - while (gameWindow.pollEvent(event)) - { - if ((event.type == sf::Event::Closed) || (sf::Keyboard::isKeyPressed(sf::Keyboard::Escape))) - gameWindow.close(); - } - player.CheckDirection(); - player.MoveSnake(playerFood, gameVideoMode); - gameWindow.clear(); - player.DisplaySnake(gameWindow); - gameWindow.draw(playerFood.snakeFoodObject); - gameWindow.display(); + GetKeyboardInput(); + player.MoveSnake(&playerFood); + RegenerateFood(); + RenderWindow(); sf::sleep(delay); } + return; +} + +void GameState::RenderWindow(void) +{ + gameWindow.clear(); + player.DisplaySnake(gameWindow); + gameWindow.draw(playerFood.GetFoodObject()); + gameWindow.display(); + return; } diff --git a/src/Snake.cpp b/src/Snake.cpp index d1216cf..b343abb 100644 --- a/src/Snake.cpp +++ b/src/Snake.cpp @@ -2,151 +2,128 @@ #include #include #include +#include "Common.h" #include "Snake.h" #include "SnakeFood.h" -// Get a new coordinate position based on snake direction -sf::Vector2f CalculateNewPosition(int direction, sf::Vector2f position) -{ - if (direction == 0) - return position; - if (direction == 1) - position.x -= 25; - if (direction == 2) - position.y -= 25; - if (direction == 3) - position.y += 25; - if (direction == 4) - position.x += 25; - return position; -} - -// Test for collision between two object positions -bool GlobalCollision(sf::Vector2f object1Position, sf::Vector2f object2Position) -{ - if (object1Position.x != object2Position.x) - return 0; - if (object1Position.y != object2Position.y) - return 0; - - return 1; -} - -// Check keyboard for new direction of snake -void Snake::CheckDirection() -{ - if (sf::Keyboard::isKeyPressed(sf::Keyboard::Left)) - snakeDirection = 1; - if (sf::Keyboard::isKeyPressed(sf::Keyboard::Up)) - snakeDirection = 2; - if (sf::Keyboard::isKeyPressed(sf::Keyboard::Down)) - snakeDirection = 3; - if (sf::Keyboard::isKeyPressed(sf::Keyboard::Right)) - snakeDirection = 4; - return; -} - -// Check snake head for running into boundaries -bool Snake::CheckBoundaries() -{ - if (snakeBody.front().getPosition().x == 0 && snakeDirection == 1) - return true; - if (snakeBody.front().getPosition().y == 0 && snakeDirection == 2) - return true; - // TODO: Change boundaries to not be hard-coded - if (snakeBody.front().getPosition().y > 675 && snakeDirection == 3) - return true; - if (snakeBody.front().getPosition().x > 975 && snakeDirection == 4) - return true; - return false; -} - -// Move snake based on direction and test for eating food -void Snake::MoveSnake(SnakeFood& snakeFood, sf::VideoMode gameVideoMode) -{ - // CheckDirection(); - sf::Vector2f newHeadPosition; - newHeadPosition = GetSnakeHeadPosition(); - if (CheckBoundaries()) - return; - newHeadPosition = CalculateNewPosition(snakeDirection, newHeadPosition); - sf::RectangleShape newBodyPart(sf::Vector2f(25,25)); - newBodyPart.setPosition(newHeadPosition); - if (IsSelfCollision(newBodyPart)) // Do nothing if self collision - { - return; - } - newBodyPart.setFillColor(sf::Color::Green); - snakeBody.push_front(newBodyPart); - if (!GlobalCollision(GetSnakeHead().getPosition(), snakeFood.snakeFoodObject.getPosition())) - snakeBody.pop_back(); - SnakeFoodCollision(snakeFood, gameVideoMode); - return; -} - -// Return the Vector2f head of snake -sf::Vector2f Snake::GetSnakeHeadPosition() -{ - sf::Vector2f position; - position = snakeBody.front().getPosition(); - return position; -} - -// Return the RectangleShape head of snake -sf::RectangleShape Snake::GetSnakeHead() -{ - sf::RectangleShape head; - head = snakeBody.front(); - return head; -} - -// Iterate through snake deque and draw to window -void Snake::DisplaySnake(sf::RenderWindow& window) -{ - for (auto it = snakeBody.cbegin(); it != snakeBody.cend(); ++it) - { - window.draw(*it); - } - return; -} - -// Test for snake self collision -bool Snake::IsSelfCollision(sf::RectangleShape testRectangle) -{ - for (auto it = snakeBody.cbegin(); it != snakeBody.cend(); ++it) - { - if (GlobalCollision(testRectangle.getPosition(), (*it).getPosition())) - { - return true; - } - } - return false; -} - -// If player collides with food then generate until no longer collided with food -void Snake::SnakeFoodCollision(SnakeFood& snakeFood, sf::VideoMode gameVideoMode) -{ - while(IsSelfCollision(snakeFood.snakeFoodObject)) - { - snakeFood.GenerateNewLocation(gameVideoMode.width, gameVideoMode.height); - } - return; -} - // General constructor for snake class -Snake::Snake() +Snake::Snake(void) { - sf::RectangleShape newBodyPart(sf::Vector2f(25,25)); + sf::RectangleShape newBodyPart(sf::Vector2f(kGridSize, kGridSize)); newBodyPart.setFillColor(sf::Color::Green); snakeBody.push_back(newBodyPart); return; } // Constructor for snake with position -Snake::Snake(sf::Vector2f head) +Snake::Snake(sf::Vector2f headSize) { - sf::RectangleShape newBodyPart(head); + sf::RectangleShape newBodyPart(headSize); newBodyPart.setFillColor(sf::Color::Green); snakeBody.push_back(newBodyPart); return; } + +// Iterate through snake deque and draw to window +void Snake::DisplaySnake(sf::RenderWindow& window) +{ + for (auto snakeBodyPart = snakeBody.cbegin(); snakeBodyPart != snakeBody.cend(); ++snakeBodyPart) + window.draw(*snakeBodyPart); + return; +} + +// Return the RectangleShape head of snake +sf::RectangleShape Snake::GetSnakeHead(void) +{ + sf::RectangleShape head; + head = snakeBody.front(); + return head; +} + +// Return the Vector2f head of snake +sf::Vector2f Snake::GetSnakeHeadPosition(void) +{ + sf::Vector2f position; + position = snakeBody.front().getPosition(); + return position; +} + +// Checks if snake position matches object position +bool Snake::IsTouchingObject(sf::RectangleShape object) +{ + for (auto snakeBodyPart = snakeBody.cbegin(); snakeBodyPart != snakeBody.cend(); ++snakeBodyPart) + { + if ((*snakeBodyPart).getPosition().x != object.getPosition().x) + continue; + if ((*snakeBodyPart).getPosition().y != object.getPosition().y) + continue; + return true; + } + return false; +} + +// Move snake based on direction and check for collision +void Snake::MoveSnake(SnakeFood* snakeFood) +{ + // TODO: Add losing on wall collision + if (CheckBoundaries()) // Wall collision + return; + sf::Vector2f newHeadPosition; + newHeadPosition = CalculateNewPosition(GetSnakeHeadPosition()); + sf::RectangleShape newBodyPart(sf::Vector2f(kGridSize, kGridSize)); + newBodyPart.setPosition(newHeadPosition); + // TODO: Add losing on self collision + if (IsSelfCollision(newBodyPart)) // Snake collision + return; + newBodyPart.setFillColor(sf::Color::Green); + snakeBody.push_front(newBodyPart); + if (!GlobalCollision(GetSnakeHeadPosition(), snakeFood->GetFoodObjectPosition())) + snakeBody.pop_back(); + return; +} + +void Snake::UpdateDirection(int newDirection) +{ + snakeDirection = newDirection; + return; +} + +// Get a new coordinate position based on snake direction +sf::Vector2f Snake::CalculateNewPosition(sf::Vector2f position) +{ + if (snakeDirection == 0) + return position; + if (snakeDirection == kLeft) + position.x -= kGridSize; + if (snakeDirection == kUp) + position.y -= kGridSize; + if (snakeDirection == kDown) + position.y += kGridSize; + if (snakeDirection == kRight) + position.x += kGridSize; + return position; +} + +// Check snake head for running into boundaries +bool Snake::CheckBoundaries(void) +{ + if (snakeBody.front().getPosition().x == 0 && snakeDirection == kLeft) + return true; + if (snakeBody.front().getPosition().y == 0 && snakeDirection == kUp) + return true; + // TODO: Change boundaries to not be hard-coded + if (snakeBody.front().getPosition().y > 675 && snakeDirection == kDown) + return true; + if (snakeBody.front().getPosition().x > 975 && snakeDirection == kRight) + return true; + return false; +} + +// Test for snake self collision +bool Snake::IsSelfCollision(sf::RectangleShape testRectangle) +{ + for (auto snakeBodyPart = snakeBody.cbegin(); snakeBodyPart != snakeBody.cend(); ++snakeBodyPart) + if (GlobalCollision(testRectangle.getPosition(), (*snakeBodyPart).getPosition())) + return true; + return false; +} diff --git a/src/SnakeFood.cpp b/src/SnakeFood.cpp index 4a1dd0f..b1989e4 100644 --- a/src/SnakeFood.cpp +++ b/src/SnakeFood.cpp @@ -1,41 +1,50 @@ // SnakeFood.cpp #include #include +#include "Common.h" #include "SnakeFood.h" -#include "Snake.h" + SnakeFood::SnakeFood() { - snakeFoodObject.setSize(sf::Vector2f(25,25)); + snakeFoodObject.setSize(sf::Vector2f(kGridSize, kGridSize)); snakeFoodObject.setFillColor(sf::Color::Red); + return; } SnakeFood::SnakeFood(sf::Vector2f snakeFoodSize) { snakeFoodObject.setSize(snakeFoodSize); snakeFoodObject.setFillColor(sf::Color::Red); + return; } -void SnakeFood::GenerateNewLocation(int horizontalLocation, int verticalLocation) +// Returns a new food object for the snakeFood +void SnakeFood::GenerateNewFood(sf::Vector2f windowSize) { sf::Vector2f newPosition; - newPosition.x = GenerateRandomNumber(horizontalLocation); - newPosition.y = GenerateRandomNumber(verticalLocation); - if (GlobalCollision(snakeFoodObject.getPosition(), newPosition)) - { - std::cout << "Location error: " << newPosition.x << " " << newPosition.y << '\n'; - throw std::runtime_error("Error! New generation on same location"); - } + newPosition.x = GenerateRandomNumber(windowSize.x); + newPosition.y = GenerateRandomNumber(windowSize.y); snakeFoodObject.setPosition(newPosition); return; } +sf::RectangleShape SnakeFood::GetFoodObject(void) +{ + return snakeFoodObject; +} +sf::Vector2f SnakeFood::GetFoodObjectPosition(void) +{ + return snakeFoodObject.getPosition(); +} + +// Returns a newly generated number int SnakeFood::GenerateRandomNumber(int generationLimit) { int generatedNumber; std::uniform_int_distribution<> distribution(0, generationLimit); generatedNumber = distribution(generator); - generatedNumber -= (generatedNumber % 25); + generatedNumber -= (generatedNumber % kGridSize); return generatedNumber; } diff --git a/src/main.cpp b/src/main.cpp index bd09bb5..7a1a501 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -2,6 +2,6 @@ int main() { - GameState newGame; - newGame.startNewGame(); + GameState game; + game.StartGame(); }