Learn To Use Ncurses By Building Snake (Part 1)

Have you ever used a command line program with an interface so good, that made you wonder. How would I begin to add an interface like that to my programs? Well the answer to that question is ncurses. Which is described by Wikipedia as “a programming library providing an application programming interface (API) that allows the programmer to write text-based user interfaces”

So in short, ncurses is a C-library for creating a nice user interface in the terminal. To get started with this we are going to use ncurses to build our own terminal version of snake so let’s begin.

Installation

In order to use ncurses we’re going to need to install it, this may be different depending what operating system you’re using. I’m currently using my Mac so I installed it using the Homebrew package manager by using the following command.

brew install curses

if you’re using a different platform you will have to look up how to install it using your systems package manger. You will also need the gcc compiler and make installed on your machine in order to follow along.

Setup Make

Once you have everything installed everything you need on your machine you will need to create a makefile in order to build your project when you want to run it. Although this isn’t strictly necessary I find it ‘s a lot easier than searching through my command history every time I want to recompile my project.

So once you have created your project directory and are inside of it. You need to create your makefile, you to this by opening your favorite text editor and creating a file named ‘Makefile’ once you have created this file you are going to need to enter the following contents.

CC = gcc
CFLAGS = -g -Wall
LINKS = -lncurses
TARGET = snake
SRC_FILES = $(wildcard *.c)
OBJS = ${SRC_FILES:.c=.o}
all:
$(CC) $(CFLAGS) -c $(SRC_FILES)
$(CC) $(CFLAGS) $(LINKS) $(OBJS) -o $(TARGET)
clean:
$(RM) $(OBJS)
$(RM) $(TARGET)

In short what this file does is allow make to do two things. If you run the make command it will build all the c files in the current directory, link the ncurses library and produce an executable called snake. Whereas the make clean command will remove all compiled object files as well as the snake executable giving you a clean working directory.

Initializing Curses

Now you have your makefile you can start working on the code itself. So let’s start digging into ncurses. In order to use ncurses your program needs to be in curses mode. The function for putting your program into curses mode is initscr() this needs to be called first before any other ncurses functions otherwise they will not work correctly. Your program also needs to exit curses mode safely before the end of it’s execution or it could cause your terminal behave oddly. The function call for this is endwin() before your program exits this needs to be called so for the purposes of our snake program we will put it at the end of our main function.

Once you have entered curses mode there are some configurations that can be called in order to configure ncurses. The first of our configuration functions is cbreak() this functions turns off line buffering which makes inputted keys instantly available to the program while also still allowing key combinations like ctrl-c to close the program. There is an alternative function for disabling buffering called raw() but we won’t discuss that here.

The next configuration function we need to call is curs_set(0) this simply sets the terminal cursor to invisible so we don’t have a blinking cursor floating in the middle of our snake playing area. And the final bit of curses configuration is keypad(stdscr, TRUE) this enable the reading of special keys on the keyboard namely function keys and arrow keys.

When working with ncurses I like to extract my initialization out into it’s own little function for code clarity, so currently we should have something that looks like this.

#include <ncurses.h>
void setupcurses() {
initscr(); // Start curses mode
cbreak(); // Disable line buffering
curs_set(0); // Set cursor to invisible
keypad(stdscr, TRUE); // Enable keypad
}
int main(void) {
setupcurses(); // enter curses mode
  endwin();                  // exit curses mode safely
return 0;
}

Our program doesn’t do much yet but we can enter curses mode and have configured it correctly. If you want to know about some of the other configuration options available you can read more in depth here.

Making a Start Screen

Our next task will be crating a start screen in order to let the user know the program has started but also not start the snake moving right away. The initial thing that I started to build for this screen was a border so I could set the player area then have the welcome text sit inside it. Traditionally in snake there is a border around the player area that the play is not allow to hit so I will start there. Seems as I’m probably going to need to draw a border more than once I will put this functionality into it’s own function which can be seen below.

void drawborder(int score) {
int row, col;
getmaxyx(stdscr, row, col);
char score_msg[] = "Score: %d";
mvprintw(0, col-strlen(score_msg), score_msg, score);
move(1, 0);
for(int i = 0;i < col;i++) { // top border 
printw("#");
}
for(int i = 1;i < row - 2;i++) {
printw("#"); // left hand side border 
for(int j = 1; j < col - 1;j++) {
printw(" ");
}
printw("#"); // right hand side border
}
for(int i = 0;i < col;i++) { // bottom border 
printw("#");
}
}

So what is going on here? Well, first off we are declaring two integer variables row, and col in order to store the amount of rows and the amount of columns within the currently open terminal window. Which leades us on to the next line the function call getmaxyx(stdscr, row, col) what this function does is take a window as its first parameter then find the width and height of said window and store those values in the second and third parameters respectively.

A Quick Word About Windows

In ncurses it is possible to have multiple windows and to have them displaying different things. We won’t utilize this function while building our snake program so you might be asking if we’re not using windows then what window is passed to getmaxyx? When ncurses is initialized, a special veriable is initialized called stdscr this contains the initial window in the program when ncurses is initialized and currently in our program is the only window in existence.

Now that we have the width and the height of the terminal window we can start thinking about how to draw a border. We are going to want the players score at the top of the screen so have our string set up to display the players score. Next we have the mvprintw function which allows us to mv to a certain y, x (ncurses functions take parameters in this order) value. so the nextline prints the string on the first line (y:0) on the right hand side of the screen. The string is printed from left to right so the way to right align the function is to subtract the length of the string to be printed from the width of the screen.

Once we have printed the players score. We make a call to the move function which like all ncuses functions works in the following way move(y, x) we use this to move the cursor down one line. Once the cursor is on a new line we will be moved back to the left hand side of the screen. Then our first loop prints a row of hash key characters across the screen. using the printw function. printw is analogous to the prinf function but is uses for printing a string at the cursors current location in ncurses. Once the printw function has been called the cursor is moved one character to the right. The following two loops create a border around the terminal by using a combination of hash key characters and spaces.

Now we can draw a border we can implement another function to create the start screen.

void startscreen(int score) {
int row, col;
getmaxyx(stdscr, row, col);
char msg[] = "Welcome to snake!";
char scndmsg[] = "Press any key to begin!";
drawborder(score);
mvprintw((row)/2+1, (col-strlen(msg))/2, "%s", msg);
mvprintw((row)/2+2, (col-strlen(scndmsg))/2, "%s", scndmsg);
}

in this function we call our drawborder function but everything else is functions we have seen before. The only thing that may be different is after subtracting the col from the length of the string we divided the result by two. This allows us horizontally align the strings.

So if we update out main function we should now have something like this.

int main() {
int score = 0;
setupcurses();
startscreen(score);
refresh();
getch();
endwin();
return 0;
}

The only function that is new to us here is refresh which displays all out updates into the terminal. Before we call it, all of our changes are just stored in memory. The refresh call is needed to see the display change. There is also getch() which waits for an input from the user. This allows us to display our start screen until the user presses a key.

That’s all for part one, we will continue our snake program in part 2.


Learn To Use Ncurses By Building Snake (Part 1) was originally published in Hacker Noon on Medium, where people are continuing the conversation by highlighting and responding to this story.

You may also like...

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.

%d bloggers like this: