Skip to content

Instantly share code, notes, and snippets.

@mgiuffrida
Last active September 8, 2019 18:01
Show Gist options
  • Save mgiuffrida/97a227ec43bf2c865210efe37e52ff57 to your computer and use it in GitHub Desktop.
Save mgiuffrida/97a227ec43bf2c865210efe37e52ff57 to your computer and use it in GitHub Desktop.
// libcurses: sudo apt-get install libncurses5-dev
// g++ -o snake snake.cpp --std=c++11 -lncurses -Wall && ./snake
#include <curses.h>
#include <unistd.h>
#include <cassert>
#include <cstdlib>
#include <string>
#include <list>
#include <vector>
struct Coord {
int x;
int y;
};
class Snake {
public:
Snake(Coord head);
const Coord& head() const { return coords_.front(); }
const Coord& tail_end() const { return coords_.back(); }
void MoveTo(Coord new_head);
void MoveToAndGrow(Coord new_head);
void Grow(Coord new_tail_end);
size_t size() const { return coords_.size(); }
private:
std::list<Coord> coords_;
};
Snake::Snake(Coord head) {
coords_.push_back(head);
}
void Snake::MoveTo(Coord new_head) {
coords_.push_front(new_head);
coords_.pop_back();
}
void Snake::MoveToAndGrow(Coord new_head) {
coords_.push_front(new_head);
}
void Snake::Grow(Coord new_tail_end) {
coords_.push_back(new_tail_end);
}
class Board {
public:
enum class Cell {
kEmpty,
kSnakeHead,
kSnakeTail,
kApple,
};
enum class Direction {
kUp,
kDown,
kLeft,
kRight,
};
enum class Status {
kPlaying,
kHitWall,
kHitSnake,
};
Board(int width, int height);
~Board();
void MoveSnake(Direction direction);
void Print() const;
Status status() const { return status_; }
size_t GetScore() const;
static Coord GetNewCoord(Coord coord, Direction direction);
private:
void AddApple();
Status status_ = Status::kPlaying;
int width_;
int height_;
Snake snake_;
std::vector<std::vector<Cell>> cells_;
};
Board::Board(int width, int height) :
width_(width), height_(height),
snake_({width/2, height/2}),
cells_(height, std::vector<Cell>(width, Cell::kEmpty)) {
srand(time(nullptr));
cells_[height/2][width/2] = Cell::kSnakeHead;
for (int i = 0; i < 2; i++) {
snake_.Grow(GetNewCoord(snake_.tail_end(), Direction::kRight));
cells_[snake_.tail_end().y][snake_.tail_end().x] = Cell::kSnakeTail;
}
AddApple();
initscr();
curs_set(0);
cbreak();
noecho();
nodelay(stdscr, true);
keypad(stdscr, true);
}
Board::~Board() {
endwin();
}
void Board::MoveSnake(Direction direction) {
assert(status_ == Status::kPlaying);
Coord old_head = snake_.head();
Coord new_head = GetNewCoord(old_head, direction);
if (new_head.y < 0 || new_head.y >= height_ ||
new_head.x < 0 || new_head.x >= width_) {
status_ = Status::kHitWall;
return;
}
assert(cells_[new_head.y][new_head.x] != Cell::kSnakeHead);
if (cells_[new_head.y][new_head.x] == Cell::kSnakeTail) {
status_ = Status::kHitSnake;
return;
}
// Move the snake.
bool was_apple = cells_[new_head.y][new_head.x] == Cell::kApple;
cells_[new_head.y][new_head.x] = Cell::kSnakeHead;
cells_[old_head.y][old_head.x] = Cell::kSnakeTail;
if (was_apple) {
snake_.MoveToAndGrow(new_head);
AddApple();
} else {
cells_[snake_.tail_end().y][snake_.tail_end().x] = Cell::kEmpty;
snake_.MoveTo(new_head);
}
}
void Board::Print() const {
erase();
move(0, 0);
addstr(std::string(2*width_ + 2, '-').c_str());
addch('\n');
for (const auto& row : cells_) {
std::string row_str;
for (Cell cell : row) {
switch (cell) {
case Cell::kEmpty:
row_str += " ";
break;
case Cell::kSnakeHead:
row_str += "S";
break;
case Cell::kSnakeTail:
row_str += "s";
break;
case Cell::kApple:
row_str += "a";
break;
}
row_str += " ";
}
addch('|');
addstr(row_str.c_str());
addch('|');
addch('\n');
}
addstr(std::string(2*width_ + 2, '-').c_str());
addch('\n');
switch (status_) {
case Status::kPlaying:
addch('\n');
break;
case Status::kHitWall:
addstr("You hit a wall!\n");
printw("Score: %d", GetScore());
break;
case Status::kHitSnake:
addstr("You hit your tail!\n");
printw("Score: %d", GetScore());
break;
}
refresh();
}
size_t Board::GetScore() const {
return snake_.size();
}
Coord Board::GetNewCoord(Coord coord, Direction direction) {
Coord new_coord = coord;
switch (direction) {
case Direction::kUp:
new_coord.y -= 1;
break;
case Direction::kDown:
new_coord.y += 1;
break;
case Direction::kLeft:
new_coord.x -= 1;
break;
case Direction::kRight:
new_coord.x += 1;
break;
}
return new_coord;
}
void Board::AddApple() {
int x, y;
do {
x = rand() % width_;
y = rand() % height_;
} while (cells_[x][y] != Cell::kEmpty);
cells_[x][y] = Cell::kApple;
}
int main() {
Board board(20, 20);
board.Print();
Board::Direction direction = Board::Direction::kLeft;
while (board.status() == Board::Status::kPlaying) {
usleep(100000);
int ch = getch();
/*
int next_ch = ch;
while (next_ch != ERR) {
ch = next_ch;
next_ch = getch();
}
*/
if (ch == KEY_LEFT && direction != Board::Direction::kRight)
direction = Board::Direction::kLeft;
else if (ch == KEY_RIGHT && direction != Board::Direction::kLeft)
direction = Board::Direction::kRight;
else if (ch == KEY_UP && direction != Board::Direction::kDown)
direction = Board::Direction::kUp;
else if (ch == KEY_DOWN && direction != Board::Direction::kUp)
direction = Board::Direction::kDown;
board.MoveSnake(direction);
board.Print();
}
nodelay(stdscr, false);
getch();
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment