Skip to content

Instantly share code, notes, and snippets.

@petrvolny
Last active May 13, 2023 10:19
Show Gist options
  • Save petrvolny/5506059 to your computer and use it in GitHub Desktop.
Save petrvolny/5506059 to your computer and use it in GitHub Desktop.
Simple ncurses snake game (inspired by the famous nokia snake mobile game). I used this as an example when I was teaching Introduction to Programming class on Masaryk University during my PhD studies few years ago (https://is.muni.cz/auth/predmet/fi/podzim2011/IB001?lang=en;setlang=en)
#include <string.h>
#include <unistd.h>
#include <math.h>
#include <curses.h>
#define SNAKE_MAX_LENGTH 12
#define WIN_MSG "You are the WINNER!"
#define WELCOME_MSG "We present you the ultimate SNAKE game!!!"
#define LOST_MSG "Loooooser :-))))"
#define UP 1
#define DOWN 2
#define LEFT 3
#define RIGHT 4
#define SNAKE_CHAR 'O'
#define FOOD_CHAR 'F'
#define WALL_CHAR '#'
typedef struct SnakePart_ {
int x;
int y;
} SnakePart;
typedef struct Food_ {
int x;
int y;
} Food;
Food food;
int direction;
SnakePart snake[SNAKE_MAX_LENGTH];
int snakeLength = 1;
int row, col;
// Initialization of ncurses and the game
void initGame(void) {
srand(time(NULL));
for (int i=0; i<SNAKE_MAX_LENGTH; i++) {
snake[i].x = -1;
snake[i].y = -1;
}
initscr();
if(has_colors() == FALSE)
{ endwin();
printf("Your terminal does not support color\n");
return 1;
}
getmaxyx(stdscr, row, col);
noecho();
cbreak();
curs_set(0);
start_color(); /* Start color */
init_pair(1, COLOR_RED, COLOR_BLACK);
attron(A_BOLD);
clear();
color_set(1, NULL);
}
// Randomly add fodd spot to the level
void addFood() {
int x,y;
y = rand()%row;
x = rand()%col;
while ((mvinch(y, x) & A_CHARTEXT) != ' ') {
y = rand()%row;
x = rand()%col;
}
food.y = y;
food.x = x;
}
// Draw food on a game plan
void drawFood() {
move(food.y, food.x);
addch(FOOD_CHAR);
}
void initSnake() {
snake[1].x = 2;
snake[1].y = row/2;
snake[0].x = 3;
snake[0].y = row/2;
snakeLength = 2;
direction = RIGHT;
}
// Draw the the actual position of the snake
void drawSnake() {
int i=0;
while (snake[i].x != -1 && snake[i].y != -1) {
move(snake[i].y, snake[i].x);
addch(SNAKE_CHAR);
i++;
}
}
// Helper method for shifting snake parts
void shiftSnake() {
int i=0;
for (int i=snakeLength-1; i>=0; i--) {
snake[i+1] = snake[i];
}
}
// Helper method for the snake movement that moves snake head in a desired direction
void addHead() {
SnakePart origHead = snake[0];
shiftSnake();
switch (direction) {
case UP:
origHead.y--;
break;
case DOWN:
origHead.y++;
break;
case LEFT:
origHead.x--;
break;
case RIGHT:
origHead.x++;
break;
}
snake[0] = origHead;
}
// Helper method for snake movement that cuts the end of the snake
void removeTail() {
mvaddch(snake[snakeLength].y, snake[snakeLength].x, ' ');
snake[snakeLength].x = -1;
snake[snakeLength].y = -1;
}
// Refresh snake movement
void moveSnake() {
addHead();
checkGame();
mvaddch(snake[0].y, snake[0].x, SNAKE_CHAR);
if (snake[0].x == food.x && snake[0].y == food.y) {
snakeLength++;
addFood();
} else {
removeTail();
}
}
// Recognize snake control
void setDirection(char c) {
switch(c) {
case 'w':
direction = UP;
break;
case 's':
direction = DOWN;
break;
case 'd':
direction = RIGHT;
break;
case 'a':
direction = LEFT;
break;
}
}
// Draw the winner screen
void showWinner() {
clear();
mvprintw(row/2, col/2-strlen(WIN_MSG)/2, WIN_MSG);
refresh();
}
// Draw the welcome screen
void showWelcome() {
clear();
mvprintw(row/2, col/2-strlen(WELCOME_MSG)/2, WELCOME_MSG);
refresh();
}
// Draw the welcome screen
void showYouLoose() {
clear();
mvprintw(row/2, col/2-strlen(LOST_MSG)/2, LOST_MSG);
refresh();
}
// Draw the level walls
void drawLevel() {
for (int i=0; i<row; i++) {
mvaddch(i, 0, WALL_CHAR);
mvaddch(i, col-1, WALL_CHAR);
}
for (int i=1; i<col-1; i++) {
mvaddch(0, i, WALL_CHAR);
mvaddch(row-1, i, WALL_CHAR);
}
for (int i=25; i<55; i++) {
mvaddch(row/4, i, WALL_CHAR);
mvaddch((row/4)*3, i, WALL_CHAR);
}
}
// Check on a game state. Checked after every game step (see main())
void checkGame() {
if (snakeLength == SNAKE_MAX_LENGTH-1) {
showWinner();
timeout(-1);
getch();
exit(0);
}
if ((mvinch(snake[0].y, snake[0].x) & A_CHARTEXT) == WALL_CHAR) {
showYouLoose();
timeout(-1);
getch();
exit(0);
}
}
// Game init function
int main() {
char c;
initGame();
showWelcome();
getch();
clear();
initSnake();
drawLevel();
drawSnake();
addFood();
refresh();
// Main game loop
while (1) {
timeout(70);
c = getch();
setDirection(c);
clear();
drawLevel();
moveSnake();
checkGame();
drawSnake();
drawFood();
refresh();
}
}
@shipof123
Copy link

To continue qjnr's list:

  1. The void initGame(void) function cant return 1, instead use exit(1);

@PARKJUHONG123
Copy link

Nice code! Can I refer to this code for my school project? Of course NOT intactly.

@Agio12
Copy link

Agio12 commented Dec 7, 2022

your code has a few bugs:

  1. you need to include <stdlib.h> for the srand() function
  2. you need to include <time.h> for the time() function
  3. you need to call endwin() before you exit the game if you won or lose, else terminal behavior is not reset
  4. you need to forward declare checkGame() because it is used in moveSnake()

can u help me ? where exactly to put that endwin() ?? my terminal cant reset after win or lose,, just like u said.... any help ?

@Agio12
Copy link

Agio12 commented Dec 7, 2022

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment