Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
.github/**
build/**
5 changes: 3 additions & 2 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,9 @@ project(SDL2Test)
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake/")

find_package(SDL2 REQUIRED)
include_directories(${SDL2_INCLUDE_DIRS} src)
include_directories(${SDL2_INCLUDE_DIRS} include)

add_executable(SnakeGame src/main.cpp src/game.cpp src/controller.cpp src/renderer.cpp src/snake.cpp)
add_executable(SnakeGame src/main.cpp src/game.cpp src/controller.cpp
src/renderer.cpp src/snake.cpp src/grid.cpp src/grid_route_planner.cpp)
string(STRIP ${SDL2_LIBRARIES} SDL2_LIBRARIES)
target_link_libraries(SnakeGame ${SDL2_LIBRARIES})
37 changes: 37 additions & 0 deletions include/controller.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
#ifndef CONTROLLER_H
#define CONTROLLER_H

#include <memory>
#include <unordered_map>
#include "snake.h"

class Controller {
public:
// Methods
void HandleInput(bool &running,
const std::vector<std::unique_ptr<Snake>> &snakes,
std::vector<Uint32> &last_input_ms) const;

private:
// Keymaps
std::vector<std::unordered_map<SDL_Keycode, Snake::Direction>> keymaps {
{ // Arrow control - Player 1
{SDLK_UP, Snake::Direction::kUp},
{SDLK_DOWN, Snake::Direction::kDown},
{SDLK_LEFT, Snake::Direction::kLeft},
{SDLK_RIGHT, Snake::Direction::kRight}
},
{ // WASD control (qwerty) - Player 2
{SDLK_w, Snake::Direction::kUp},
{SDLK_s, Snake::Direction::kDown},
{SDLK_a, Snake::Direction::kLeft},
{SDLK_d, Snake::Direction::kRight}
}
};

// Methods
void ChangeDirection(const std::unique_ptr<Snake> &snake, Snake::Direction input,
Snake::Direction opposite) const;
};

#endif
65 changes: 65 additions & 0 deletions include/game.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
#ifndef GAME_H
#define GAME_H

#include <random>
#include <algorithm>
#include <future>
#include <utility>

#include "SDL.h"
#include "controller.h"
#include "renderer.h"
#include "snake.h"
#include "grid.h"
#include "grid_route_planner.h"

class Game {
public:
Game(std::size_t grid_w, std::size_t grid_h, int num_players, Uint32 inactivity_timout);

// Public methods
void Run(Controller const &controller, Renderer &renderer,
std::size_t target_frame_duration);

// Getters/Setters and invariants
std::vector<int> GetSize() const;
std::vector<int> GetScore() const { return scores; }
void SetNumPlayers(const int &num_players) {
if (num_players < 1 || num_players > 2) {
throw std::invalid_argument("The number of players must be 1 or 2");
}
n_players = num_players;
scores.assign(n_players, 0);
}

private:
std::size_t grid_width;
std::size_t grid_height;

Uint32 inactivity_ms;
std::vector<Uint32> last_input_ms;

int n_players;
std::vector<std::unique_ptr<Snake>> snakes;
SDL_Point food;

std::random_device dev;
std::mt19937 engine;
std::uniform_int_distribution<int> random_w;
std::uniform_int_distribution<int> random_h;

std::vector<int> scores;

Grid grid_model;

std::vector<std::future<std::vector<std::pair<int,int>>>> plan_futures;
std::vector<std::pair<int,int>> planned_goal;

void SubmitPlan(std::size_t i);
void TryInstallPlan(std::size_t i);

void PlaceFood();
void Update();
};

#endif
41 changes: 41 additions & 0 deletions include/grid.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
#ifndef GRID_H
#define GRID_H

#include <iostream>
#include <vector>

enum class State {kEmpty, kObstacle, kClosed, kPath, kStart, kFinish};

struct Node {
State state = State::kEmpty;
int x = 0;
int y = 0;
int h_value = 0;
int g_value = 0;
bool visited = false;
Node * parent = nullptr;
std::vector<Node *> neighbors;

int distance(Node other_node) const;
};

class Grid {
public:
Grid(std::size_t w, std::size_t h);

void Reset();
void MarkFood(int x, int y);
void MarkObstacle(int x, int y);

Node &At(int x, int y) { return cells[x][y]; }
const std::vector<std::vector<Node>> &Data() const { return cells; }

std::size_t Width() const { return width; }
std::size_t Height() const { return height; }

private:
std::size_t width, height;
std::vector<std::vector<Node>> cells;
};

#endif
32 changes: 32 additions & 0 deletions include/grid_route_planner.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
#ifndef GRID_ROUTE_PLANNER_H
#define GRID_ROUTE_PLANNER_H

#include <iostream>
#include <vector>
#include <string>

#include <grid.h>

class RoutePlanner {
public:
RoutePlanner(Grid &grid_model, Node *start_node, Node *goal_node);

// Public methods
void CalculateHValue(Node *node);
void AddNeighbors(Node *current_node);
std::vector<Node*> ConstructFinalPath(Node *current_node);
Node *NextNode();
void AStarSearch();
const std::vector<Node*>& GetPlan() const { return plan; }
const std::vector<std::pair<int,int>>& GetPlanCells() const { return plan_cells; }

private:
Grid &model;
std::vector<Node*> open_list;
Node *start_node = nullptr;
Node *end_node = nullptr;
std::vector<Node*> plan;
std::vector<std::pair<int,int>> plan_cells;
};

#endif
7 changes: 5 additions & 2 deletions src/renderer.h → include/renderer.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
#define RENDERER_H

#include <vector>
#include <memory>

#include "SDL.h"
#include "snake.h"

Expand All @@ -11,8 +13,9 @@ class Renderer {
const std::size_t grid_width, const std::size_t grid_height);
~Renderer();

void Render(Snake const snake, SDL_Point const &food);
void UpdateWindowTitle(int score, int fps);
void Render(const std::vector<std::unique_ptr<Snake>> &snakes, SDL_Point const &food);
void UpdateWindowTitle(const std::vector<int> &scores, int fps);
int ShowPlayerMenu();

private:
SDL_Window *sdl_window;
Expand Down
68 changes: 68 additions & 0 deletions include/snake.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
#ifndef SNAKE_H
#define SNAKE_H

#include <vector>
#include <queue>

#include "SDL.h"
#include "grid_route_planner.h"

class Snake {
public:
enum class Direction { kUp, kDown, kLeft, kRight };
enum class Mode { Manual, Autonomous };

Snake(int grid_width, int grid_height, int dx, int dy)
: grid_width(grid_width),
grid_height(grid_height),
head_x(grid_width / 2 + dx),
head_y(grid_height / 2 + dy) {}

void Update();
void GrowBody();
bool SnakeCell(int x, int y);

void SetMode(Mode m) { mode = m; };
Mode GetMode() const { return mode; };
bool IsAutonomous() { return mode == Mode::Autonomous ? true : false ; };

void SetPlannedPath(const std::vector<Node*> &path) {
planned_path.clear();
for (auto* p : path) planned_path.push_back(p);
}
void ClearPlannedPath() { planned_path.clear(); }
bool HasPlannedPath() const { return !planned_path.empty(); }

Direction direction = Direction::kUp;
Mode mode{Mode::Manual};

float speed{0.1f};
int size{1};
bool alive{true};
float head_x;
float head_y;
std::vector<SDL_Point> body;
bool replan_requested{false};

private:
void UpdateHeadManual();
void UpdateHeadAutonomous();
void UpdateBody(SDL_Point &current_cell, SDL_Point &prev_cell);
static Direction Opposite(Direction d) {
switch (d) {
case Direction::kUp: return Direction::kDown;
case Direction::kDown: return Direction::kUp;
case Direction::kLeft: return Direction::kRight;
case Direction::kRight: return Direction::kLeft;
}
return d;
}

bool growing{false};
int grid_width;
int grid_height;

std::deque<Node*> planned_path;
};

#endif
60 changes: 36 additions & 24 deletions src/controller.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,39 +3,51 @@
#include "SDL.h"
#include "snake.h"

void Controller::ChangeDirection(Snake &snake, Snake::Direction input,
void Controller::ChangeDirection(const std::unique_ptr<Snake> &snake, Snake::Direction input,
Snake::Direction opposite) const {
if (snake.direction != opposite || snake.size == 1) snake.direction = input;
if (snake->direction != opposite || snake->size == 1) snake->direction = input;
return;
}

void Controller::HandleInput(bool &running, Snake &snake) const {
void Controller::HandleInput(bool &running,
const std::vector<std::unique_ptr<Snake>> &snakes,
std::vector<Uint32> &last_input_ms) const {
SDL_Event e;
while (SDL_PollEvent(&e)) {
if (e.type == SDL_QUIT) {
running = false;
} else if (e.type == SDL_KEYDOWN) {
switch (e.key.keysym.sym) {
case SDLK_UP:
ChangeDirection(snake, Snake::Direction::kUp,
Snake::Direction::kDown);
break;

case SDLK_DOWN:
ChangeDirection(snake, Snake::Direction::kDown,
Snake::Direction::kUp);
break;

case SDLK_LEFT:
ChangeDirection(snake, Snake::Direction::kLeft,
Snake::Direction::kRight);
break;

case SDLK_RIGHT:
ChangeDirection(snake, Snake::Direction::kRight,
Snake::Direction::kLeft);
break;
}
for (std::size_t i = 0; i < snakes.size() && i < keymaps.size(); ++i) {
// Get keymap
const auto &map = keymaps[i];
// Search for key
auto it = map.find(e.key.keysym.sym);
// Skip if no key found
if (it == map.end()) continue;

// Otherwise:
// Update last input to detect player inactivity
last_input_ms[i] = SDL_GetTicks();
// Map key
switch (it->second) {
case Snake::Direction::kUp:
ChangeDirection(snakes[i], Snake::Direction::kUp,
Snake::Direction::kDown);
break;
case Snake::Direction::kDown:
ChangeDirection(snakes[i], Snake::Direction::kDown,
Snake::Direction::kUp);
break;
case Snake::Direction::kLeft:
ChangeDirection(snakes[i], Snake::Direction::kLeft,
Snake::Direction::kRight);
break;
case Snake::Direction::kRight:
ChangeDirection(snakes[i], Snake::Direction::kRight,
Snake::Direction::kLeft);
break;
}
}
}
}
}
15 changes: 0 additions & 15 deletions src/controller.h

This file was deleted.

Loading