Never really got past the beginner stage of programming so I'm trying to get better this Summer. I think games are a fun way to learn how to program so I decided to start with snake. I'd appreciate any feedback or learning resources you guys think might be helpful. Cheers!
How it Works
From the menu screen, when the user presses play, the game initializes (the snake body, food generator, and game clock are dynamically allocated). I keep these game elements in an anonymous namespace but I'm not sure if that's appropriate. After initialization, the game loop runs. Basically, Check for Collisions -> Check if Eating -> Update Snake Nodes -> Render and Display.
Game.cpp
#include "Game.h"
namespace
{
    SnakeBody *snakebody;
    FoodGenerator *foodgenerator;
    sf::Clock *gameclock;
}
void Game::Start()
{
    if (mGameState != UNINITIALIZED)
        return;
    mMainWindow.create(sf::VideoMode(windowparameters::RESOLUTION_X, windowparameters::RESOLUTION_Y, windowparameters::COLOR_DEPTH), "Snake!");
    mGameState = SHOWING_MENU;
    while (mGameState != EXITING)
        GameLoop();
    mMainWindow.close();
}
void Game::ShowMenuScreen()
{
    MainMenu menuScreen;
    MainMenu::MenuResult result = menuScreen.Show(mMainWindow);
    switch (result)
    {
    case MainMenu::Exit:
        mGameState = EXITING;
        break;
    case MainMenu::Play:
        mGameState = RUNNING;
        break;
    }
}
Game::GameState Game::WaitForEnterOrExit()
{
    GameState nextstate = GAMEOVER;
    sf::Event currentevent;
    while (nextstate != EXITING && nextstate != RUNNING)
    {
        while (mMainWindow.pollEvent(currentevent))
        {
            if (currentevent.type == sf::Event::EventType::KeyPressed && 
                sf::Keyboard::isKeyPressed(sf::Keyboard::Enter))
            {
                nextstate = RUNNING;
            }
            else if (currentevent.type == sf::Event::EventType::Closed)
            {
                nextstate = EXITING;
            }
        }
    }
    return nextstate;
}
void Game::InitializeGameElements()
{
    snakebody = new SnakeBody();
    foodgenerator = new FoodGenerator(windowparameters::RESOLUTION_X, windowparameters::RESOLUTION_Y, windowparameters::UNIT_SPACING);
    gameclock = new sf::Clock();
}
void Game::CleanupGameElements()
{
    delete(gameclock);
    delete(snakebody);
    delete(foodgenerator);
}
void Game::HandleEvents()
{
    sf::Event currentevent;
    while (mMainWindow.pollEvent(currentevent))
    {
        if (currentevent.type == sf::Event::EventType::KeyPressed)
        {
            if (sf::Keyboard::isKeyPressed(sf::Keyboard::Left))
            {
                snakebody->RedirectHead(SnakeBody::LEFT);
            }
            else if (sf::Keyboard::isKeyPressed(sf::Keyboard::Right))
            {
                snakebody->RedirectHead(SnakeBody::RIGHT);
            }
            else if (sf::Keyboard::isKeyPressed(sf::Keyboard::Up))
            {
                snakebody->RedirectHead(SnakeBody::UP);
            }
            else if (sf::Keyboard::isKeyPressed(sf::Keyboard::Down))
            {
                snakebody->RedirectHead(SnakeBody::DOWN);
            }
            break;
        }
        else if (currentevent.type == sf::Event::EventType::Closed)
        {
            mGameState = EXITING;
            mMainWindow.close();
        }
    }
}
void Game::GameTick()
{
    // tick scene
    if (gameclock->getElapsedTime().asMilliseconds() >= windowparameters::TIC_RATE_IN_MS)
    {
        // Check Collision with body
        if (snakebody->CheckCollision())
            mGameState = GAMEOVER;
        else if (snakebody->CheckEating(foodgenerator->mGraphic))
        {
            snakebody->IncrementSegments();
            foodgenerator->mUneaten = false;
            std::cout << "SCORE = " << snakebody->mNumSegments << std::endl;
        }
        // update snake
        snakebody->UpdateSegments(0, windowparameters::RESOLUTION_X, 0, windowparameters::RESOLUTION_Y);
        // update food
        if (!foodgenerator->mUneaten)
            foodgenerator->Generate(snakebody);
        // reset screen, render, display
        mMainWindow.clear(sf::Color(230, 230, 230));
        mMainWindow.draw(foodgenerator->mGraphic);
        snakebody->DrawSegments(mMainWindow);
        mMainWindow.display();
        gameclock->restart();
    }
}
void Game::GameLoop()
{
    while (true)
    {
        switch (mGameState)
        {
        case SHOWING_MENU:
            ShowMenuScreen();
            break;
        case GAMEOVER:
            mGameState = WaitForEnterOrExit();
            break;
        case RUNNING:
            InitializeGameElements();
            // run game loop
            while (mMainWindow.isOpen() && mGameState == RUNNING)
            {
                HandleEvents();
                GameTick();
            }
            CleanupGameElements();
            break;
        case EXITING:
            mMainWindow.close();
            break;
        default:
            mMainWindow.close();
            break;
        }
    }
}
// Because Game is a static class, the member variables need to be instantiated MANUALLY
Game::GameState Game::mGameState = Game::UNINITIALIZED;
sf::RenderWindow Game::mMainWindow;
Game.h
#pragma once
#include <cstdint>
#include <iostream>
#include "FoodGenerator.h"
#include "MainMenu.h"
#include "SFML\Window.hpp"
#include "SFML\Graphics.hpp"
#include "SnakeBody.h"
namespace windowparameters
{
    const uint16_t RESOLUTION_X = 1024;
    const uint16_t RESOLUTION_Y = 768;
    const uint8_t COLOR_DEPTH = 32;
    const uint16_t TIC_RATE_IN_MS = 60;
    const uint8_t UNIT_SPACING = 32;
}
class Game
{
public:
    static void Start();
private:
    enum GameState { UNINITIALIZED, SHOWING_MENU, RUNNING, EXITING, GAMEOVER };
    static void GameLoop();
    static void ShowMenuScreen();
    static void InitializeGameElements();
    static void CleanupGameElements();
    static void HandleEvents();
    static void GameTick();
    static GameState WaitForEnterOrExit();
    static GameState mGameState;
    static sf::RenderWindow mMainWindow;
};
FoodGenerator.h
#pragma once
#include <random>
#include "Coordinate.h"
#include "SnakeBody.h"
class FoodGenerator
{
public:
    FoodGenerator::FoodGenerator(int xmax, int ymax, int spacing);
    Coordinate Generate(SnakeBody *snakeBody);
    bool mUneaten;
    int mXMax;
    int mYMax;
    int mSpacing;
    Coordinate mCurrentLocation;
    sf::RectangleShape mGraphic;
private:
    std::uniform_int_distribution<int> uniX;
    std::uniform_int_distribution<int> uniY;
    std::random_device rd;
    std::mt19937 rng;
};
FoodGenerator.cpp
#include "FoodGenerator.h"
FoodGenerator::FoodGenerator(int xmax, int ymax, int spacing)
{
    rng = std::mt19937(rd());    // random-number engine used (Mersenne-Twister in this case)
    uniX = std::uniform_int_distribution<int>(1, xmax/spacing - 1); // guaranteed unbiased
    uniY = std::uniform_int_distribution<int>(1, ymax/spacing - 1); // guaranteed unbiased
    mGraphic = sf::RectangleShape(sf::Vector2f(spacing, spacing));
    mGraphic.setFillColor(sf::Color(0, 0, 128));
    mGraphic.setOrigin(0, 0);
    mUneaten = false;
    mXMax = xmax;
    mYMax = ymax;
    mSpacing = spacing;
}
Coordinate FoodGenerator::Generate(SnakeBody *snakeBody)
{
    bool freePosFound = false;
    int xPos, yPos;
    std::list<SnakeBody::SnakeSegment>::iterator it, head, end;
    it = snakeBody->mSegments.begin();
    head = snakeBody->mSegments.begin();
    end = snakeBody->mSegments.end();
    while (!freePosFound)
    {
        xPos = uniX(rng);
        yPos = uniY(rng);
        mGraphic.setPosition(xPos*mSpacing, yPos*mSpacing);
        while (it != end)
        {
            if (it->mGraphic.getGlobalBounds().intersects(mGraphic.getGlobalBounds()))
            {
                it = head;
                break;
            }
            it++;
        }
        if (it == end)
            freePosFound = true;
    }
    mUneaten = true;
    return Coordinate(xPos, yPos);
}
MainMenu.h
#pragma once
#include <list>
#include "SFML\Graphics.hpp"
class MainMenu
{
public:
    enum MenuResult {Nothing, Exit, Play};
    struct MenuItem
    {
        MenuResult action;
        sf::Rect<int> rect;
    };
    MenuResult Show(sf::RenderWindow& window);
private:
    MenuResult GetMenuResponse(sf::RenderWindow& window);
    MenuResult HandleClick(int x, int y);
    std::list<MenuItem> mMenuItems;
};
MainMenu.cpp
#include "MainMenu.h"
MainMenu::MenuResult MainMenu::Show(sf::RenderWindow& window)
{
    sf::Texture image;
    image.loadFromFile("C:/Users/Carter/Pictures/snake_menu.jpg");
    sf::Sprite sprite(image);
    MenuItem playButton;
    playButton.rect.left = 200;
    playButton.rect.top = 525;
    playButton.rect.width = 600;
    playButton.rect.height = 100;
    playButton.action = Play;
    MenuItem exitButton;
    exitButton.rect.left = 200;
    exitButton.rect.top = 630;
    exitButton.rect.width = 600;
    exitButton.rect.height = 100;
    exitButton.action = Exit;
    mMenuItems.push_back(playButton);
    mMenuItems.push_back(exitButton);
    window.draw(sprite);
    window.display();
    return GetMenuResponse(window);
}
MainMenu::MenuResult MainMenu::HandleClick(int x, int y)
{
    std::list<MenuItem>::iterator it;
    for (it = mMenuItems.begin(); it != mMenuItems.end(); it++)
    {
        sf::Rect<int> menuItemRect = (*it).rect;
        if((x > menuItemRect.left) &&
            (x < (menuItemRect.left + menuItemRect.width)) &&
            (y > menuItemRect.top) &&
            (y < (menuItemRect.top + menuItemRect.height)))
        {
            return (*it).action;
        }
    }
    return Nothing;
}
MainMenu::MenuResult MainMenu::GetMenuResponse(sf::RenderWindow& window)
{
    sf::Event menuEvent;
    while (true)
    {
        while (window.pollEvent(menuEvent))
        {
            if (menuEvent.type == sf::Event::EventType::MouseButtonPressed)
                return HandleClick(menuEvent.mouseButton.x, menuEvent.mouseButton.y);
            if (menuEvent.type == sf::Event::EventType::Closed)
                return Exit;
        }
    }
}
SnakeBody.h
#pragma once
#include <cstdint>
#include <list>
#include "Coordinate.h"
#include "SFML\Graphics.hpp"
class SnakeBody
{
public:
    SnakeBody();
    enum SnakeDirection { LEFT, RIGHT, UP, DOWN };
    class SnakeSegment
    {
    public:
        SnakeSegment(int x, int y, SnakeDirection dir);
        void UpdatePosition();
        bool CheckBounds(int xmin, int xmax, int ymin, int ymax);
        Coordinate GetPosition();
        void SetPosition(int x, int y);
        SnakeDirection GetDirection();
        void SetDirection(SnakeDirection dir);
        sf::RectangleShape mGraphic;
    private:
        Coordinate mPosition;
        SnakeDirection mDirection;
    };
    void UpdateSegments(int xmin, int xmax, int ymin, int ymax);
    void DrawSegments(sf::RenderWindow &window);
    void RedirectHead(SnakeDirection newDir);
    void IncrementSegments();
    bool CheckCollision();
    bool CheckEating(sf::RectangleShape foodGraphic);
    int mNumSegments;
    std::list<SnakeSegment> mSegments;
};
SnakeBody.cpp
#include "SnakeBody.h"
namespace
{
    const uint8_t SNAKE_MOVE_PER_TICK = 32;
    const uint8_t BODY_DIM = 32;
}
SnakeBody::SnakeSegment::SnakeSegment(int x, int y, SnakeBody::SnakeDirection dir)
{
    SetPosition(x, y);
    SetDirection(dir);
    mGraphic = sf::RectangleShape(sf::Vector2f(BODY_DIM, BODY_DIM));
    mGraphic.setFillColor(sf::Color(34, 139, 34));
    mGraphic.setOrigin(BODY_DIM / 2, BODY_DIM / 2);
    mGraphic.setPosition(sf::Vector2f(x, y));
}
Coordinate SnakeBody::SnakeSegment::GetPosition()
{
    return mPosition;
}
void SnakeBody::SnakeSegment::SetPosition(int x, int y)
{
    mPosition.mXCoord = x;
    mPosition.mYCoord = y;
    mGraphic.setPosition(sf::Vector2f(x, y));
}
SnakeBody::SnakeDirection SnakeBody::SnakeSegment::GetDirection()
{
    return mDirection;
}
void SnakeBody::SnakeSegment::SetDirection(SnakeBody::SnakeDirection dir)
{
    // prevent 180 degree turns about the head
    switch (dir)
    {
    case LEFT:
        if (mDirection == RIGHT)
            return;
        break;
    case RIGHT:
        if (mDirection == LEFT)
            return;
        break;
    case UP:
        if (mDirection == DOWN)
            return;
        break;
    case DOWN:
        if (mDirection == UP)
            return;
        break;
    }
    SnakeSegment::mDirection = dir;
}
bool SnakeBody::SnakeSegment::CheckBounds(int xmin, int xmax, int ymin, int ymax)
{
    bool wrapped = false;
    int xrange = xmax - xmin;
    int yrange = ymax - ymin;
    // check bounds and wrap
    if (mPosition.mXCoord < xmin)
    {
        mPosition.mXCoord += xrange;
        wrapped = true;
    }
    else if (mPosition.mXCoord > xmax)
    {
        mPosition.mXCoord %= xrange;
        wrapped = true;
    }
    else if (mPosition.mYCoord < ymin)
    {
        mPosition.mYCoord += yrange;
        wrapped = true;
    }
    else if (mPosition.mYCoord > ymax)
    {
        mPosition.mYCoord %= yrange;
        wrapped = true;
    }
    if(wrapped)
        mGraphic.setPosition(mPosition.mXCoord, mPosition.mYCoord);
    return wrapped;
}
void SnakeBody::SnakeSegment::UpdatePosition()
{
    // check direction and increment
    switch (mDirection)
    {
    case LEFT:
        mPosition.IncrementX(-SNAKE_MOVE_PER_TICK);
        break;
    case RIGHT:
        mPosition.IncrementX(SNAKE_MOVE_PER_TICK);
        break;
    case UP:
        mPosition.IncrementY(-SNAKE_MOVE_PER_TICK);
        break;
    case DOWN:
        mPosition.IncrementY(SNAKE_MOVE_PER_TICK);
        break;
    }
    mGraphic.setPosition(sf::Vector2f(mPosition.mXCoord, mPosition.mYCoord));
}
SnakeBody::SnakeBody()
{
    SnakeBody::SnakeSegment headSegment(BODY_DIM/2, BODY_DIM/2, RIGHT);
    //SnakeBody::SnakeSegment testSegment(100 - BODY_DIM, 100, RIGHT);
    mNumSegments = 1;
    mSegments.push_back(headSegment);
    //_segments.push_back(testSegment);
}
void SnakeBody::UpdateSegments(int xmin, int xmax, int ymin, int ymax)
{
    // update segments starting at tail
    std::list<SnakeSegment>::iterator front, it, next, end;
    it = --mSegments.end();
    end = mSegments.end();
    if (mNumSegments > 1)
        next = --(--mSegments.end());
    else
        next = end;
    front = mSegments.begin();
    for(int i=0; i < mNumSegments; i++)
    {
        // increment position
        it->UpdatePosition();
        it->CheckBounds(xmin, xmax, ymin, ymax);
        // update direction for non-head nodes
        if ((it != front) && (it->GetDirection() != next->GetDirection())){
            it->SetDirection(next->GetDirection());
        }
        if ((next != front) && next != end)
            next--;
        if (it != front)
            it--;
    }
}
void SnakeBody::DrawSegments(sf::RenderWindow &window)
{
    std::list<SnakeSegment>::iterator it = mSegments.begin();
    std::list<SnakeSegment>::iterator end = mSegments.end();
    while (it != end)
    {
        window.draw(it->mGraphic);
        it++;
    }
}
void SnakeBody::RedirectHead(SnakeBody::SnakeDirection newDir)
{
    std::list<SnakeSegment>::iterator head = mSegments.begin();
    head->SetDirection(newDir);
}
void SnakeBody::IncrementSegments()
{
    // find location of last node
    std::list<SnakeSegment>::iterator tail = --mSegments.end();
    // spawn at offset location
    int newX, newY;
    newX = (tail->GetPosition()).mXCoord;
    newY = (tail->GetPosition()).mYCoord;
    switch (tail->GetDirection())
    {
    case LEFT:
        newX += BODY_DIM;
        break;
    case RIGHT: 
        newX -= BODY_DIM;
        break;
    case UP:
        newY += BODY_DIM;
        break;
    case DOWN:
        newY -= BODY_DIM;
        break;
    }
    SnakeSegment newSegment(newX, newY, tail->GetDirection());
    mSegments.push_back(newSegment);
    mNumSegments++;
}
bool SnakeBody::CheckCollision()
{
    sf::RectangleShape headRect = (mSegments.begin())->mGraphic;
    std::list<SnakeSegment>::iterator it = ++mSegments.begin();
    for (int i = 1; i < mNumSegments; i++, it++)
    {
        if (headRect.getGlobalBounds().intersects(it->mGraphic.getGlobalBounds()))
            return true;
    }
    return false;
}
bool SnakeBody::CheckEating(sf::RectangleShape foodGraphic)
{
    std::list<SnakeSegment>::iterator head = mSegments.begin();
    return head->mGraphic.getGlobalBounds().intersects(foodGraphic.getGlobalBounds());
}
main.cpp
#include "Game.h"
int main()
{
    Game::Start();
    return 0;
}

Coordinate.h. \$\endgroup\$