roguelike-tutorial

Roguelike Tutorial Revised ported to C

View on GitHub

Chapter 0: Setting Up Our Environment

Prior Knowledge

We’re going to start with some assumptions about you, the reader. In order to speed up the writing of this tutorial, I’m going to assume that you have some basic programming knowledge such as:

You do not need to know how to program in C. I will cover any language specifics such as header files, the standard library, and even pointers. Having prior knowledge about C will be helpful, no doubt, but it is not a requirement.

Then again, you’re free to to ignore all the above assumptions and forge on ahead anyway. More power to you. The only hard requirement is the first, as I will not be providing instructions for native Windows development or MacOS. If you are on Windows I suggest msys2 with the mingw64 toolchain. You can also use the Windows Subsystem for Linux. Setting either up is an exercise left to the reader, though I may come back later and expand the on the subject.

Reading This Tutorial

Because this tutorial is written to both teach you the ins and outs of C and also how to make a simple roguelike, there’s a lot of jumping around as we move between coding our game and talking about C specific subjects. When you see a section of the tutorial that starts with Under The C: this means that we’re going to step away from talking about building our game to discuss C itself.

If you’re already experienced with C, most of the information in these sidebars will be review. Feel free to skip them if, for example, you do not need an explanation on argc and argv.

Each chapter of the tutorial has a branch in the git repository with the code available for you to look at. There will also be a link at the end of each chapter to its respective branch. Use this as a tool if your game is going haywire and you can’t figure out where you made a mistake.

One convention I use it to try to mark variable and function names as code. For example, an int named i will by represented as i in the text of the tutorial. Likewise, a function like “printf” will be represented as print(). The () in the name marks that it is a function and has no representation of any arguments that may or may not be required by that function.

Getting BearLibTerminal

BearLibTerminal is the display library we will be using in this tutorial. You can download the header file and static libraries from the documentation website. Alternatively, I will provide a link below with a scaffold to start the tutorial that includes the necessary files for using BearLibTerminal.

“BearLibTerminal” is a lot to type, so throughout this tutorial you will see the acronym BLT. As much as I love a tasty sandwich, BLT will refer to BearLibTerminal.

Editors

C files are just code in plaintext, so you can use anything from Vim to VSCode to even Notepad. While not required, having some syntax highlighting and hints can be helpful. Personally I use Vim or VSCode depending on which computer I am using, but choose what you want.

One thing I would like to point out specifically is that VSCode’s C/C++ extension will sometimes mark calls to malloc() as an error if you do not cast the call to malloc(). This is an error in C++ but not an error in C. You can attempt to abate the errors by forcing the language to C instead of C++ for that file.

If you have no idea what the last sentence meant and you’re a VSCode user, do not worry. We will cover specifically what I mean when we get there later.

The Project Folder

Here is what our initial project folder looks like:

roguelike-tutorial/
  include/
    BearLibTerminal.h
  libs/
    BearLibTerminal.dll
    libBearLibTerminal.so
  src/
    main.c
  Makefile

Let’s talk about each folder and what’s in it. we will also cover some C basics ahead.

include

Time for our first lesson on how C works. C files are usually (but not always) a pair of files: file.h and file.c. Technically you can name them with whatever extension you want, but 40+ years of standard practice is to name header files with .h and code files with .c.

Header files are what other pieces of your code read. The header basically says “Here are the things that I provide for other code to use”. Let’s start with a basic example.

/* hello.h */
void hello();
/* hello.c */
#include <stdio.h>

void hello() {
    printf("Hello!\n");
}

The /* and */ mark the beginning and end of a comment. Comments can span multiple lines and the C compiler will throw an error if you forget the */ at the end of your comment. Modern versions of C also support single line comments that start with // and comment out only the text after it on the same line, but I do not use those. Feel free to use // if you prefer it.

In hello.h, we declare the function hello(). We state that there will be a function named hello() and that it returns nothing aka void. All C statements must end with a semicolon, unlike Python for example which uses a newline to make the end of a statement.

We will talk about the contents of main.c in the next section.

By convention, include files go into a folder called include. Right now we only have one header file, BearLibTerminal.h. This is the header for BLT that is provided by the developer. Like hello.h above, describes the functions and definitions that we can use. Any time we use a piece of BLT, we have to put #include "BearLibTerminal.h" at the top.

One more thing before we move on: You may ask why I used angle brackets for #include <stdio.h> and double quotes for #include "BearLibTerminal.h". When using a header that is installed for the system, you use angle quotes. Examples would be any headers from the C standard library or a library you have installed with your package manager. Double quotes are used for header files that exist inside your project. Since we will let our compiler know that the include folder is where we keep our headers, we just need to put the name of the header in double quotes and not the full path.

src

Like include, the folder src is by convention the folder that your .c files go in. Technically you can call it source or c_files or whatever you want, but I always follow the convention. Inside we have main.c, which is where we start our program. Once again, you may call main.c whatever you want as long as it has a main() function, but I highly recommend that you stick to convention here.

Now lets look at our example hello.c above. #include <stdio.h> tells our compiler to reference the stdio.h header from the C standard library which includes, among other things, a function that prints text to the terminal called printf(). This lets us use printf() in any function inside the file.

Next, we define hello() by stating what hello() actually does. C uses curly braces to lump blocks of code together, that way the function knows what code it contains. We call printf() and pass in the string Hello!\n. Why a \n at the end? Because printf() does not write a newline unless told to. This is different from, for example, Python’s print() function which assumes you want a newline after printing and does so automatically.

libs

In the libs folder, I have placed the compiled BearLibTerminal library files for Linux and Windows. We could just place them in the root of our folder, but I like to have a clean root in my projects.

Makefile

A Makefile is a script that controls make, a standard UNIX-like build tool. Specifically, I will be using GNU Make, which is the standard on most Linux distros. There are giant books just on Makefiles and how they work and explaining Makefiles is way beyond the scope of this tutorial, so we’re going to go with a very simple one that you can copy. If you’d like to know more, there is an excellent tutorial online. Here is the entirety of Makefile:

CC = gcc
IDIR = include
LDIR = libs
CFLAGS = -std=c11 -I$(IDIR) -L$(LDIR)
LIB = -lBearLibTerminal
RPATH = -Wl,-rpath .
SRC = src/*.c
OUT = rtut

all:
ifeq ($(OS),Windows_NT)
	@cp libs/BearLibTerminal.dll ./
	$(CC) $(CFLAGS) $(LIB) $(SRC) -o $(OUT)
else
	@cp libs/libBearLibTerminal.so ./
	$(CC) $(CFLAGS) $(LIB) $(SRC) $(RPATH) -o $(OUT)
endif

clean:
	rm -f rtut
	rm -f *.dll
	rm -f *.so

The first section is defining variables. Any time you see $(VARIABLE_NAME), we substitues the $() part with what’s contained in the variable. $(CC) become gcc, etc. Variables can expand other variables, so if IDIR expands to include, then -I$(IDIR) expands to -Iinclude. we will come back to these later and explain what they mean.

all: and clean: are different sections that we can pass as arguments to make. It will run only the section that we specify.

all: is where the magic happens. Since all: as our first section, it is the default section that make will run if you do not pass an argument. This means that running make and make all will have the same effect. clean: runs the three rm commands listed below it deleting our game and its library files from our project directory. This is just a small helper script to cleanup our root directory.

When we want to compile our program, we can just type make into the terminal with no arguments. This means that all: is called as it’s the first section of the Makefile. make then runs the all: section.

The ifeq checks to see if the built-in OS variable is the string Windows_NT. This tells us if we’re currently building on a Windows computer. If so, we copy the dll of BearLibTerminal to our project root and then run the compiler command. If we are not on Windows, then the else section runs and we copy the Linux library instead and runs the Linux version of our compiler command.

Let’s look at the Windows version of our compiler command first. Our variables expand before the line is run by make, so $(CC) $(CFLAGS) $(LIB) $(SRC) -o $(OUT) becomes gcc -std=c11 -Iinclude -L. -lBearLibTerminal src/*.c -o rtut. Let’s discuss what all that means:

On Linux, you’ll notice an extra section called RPATH that resolves to -Wl,-rpath . which is some weird looking syntax. Linux assumes that libraries are in the system library paths like /usr/lib. Since we have BearLibTerminal’s library file in our project directory instead, we need to tell the compiler that it should look in our project directory for BLT and link to it there instead of in the system library paths. The reason we do not do this on Windows is that packing libraries with your program is the standard way to distribute Windows software, so when compiling on Windows most major compilers assume that your library files are in your project and check automatically.

Putting It All Together

Ok, that was a lot of information.. and we do not even have a compiled program yet! Now that our directories are built and Makefile created, let’s open up src/main.c and get a BLT Hello World working. do not worry about the contents of the file, in the next chapter we will dive into the details of what this code actually does.

#include "BearLibTerminal.h"

int main(int argc, char** argv)
{
    terminal_open();

    // Printing text
    terminal_print(1, 1, "Hello, world!");
    terminal_refresh();

    // Wait until user closes the window
    while (terminal_read() != TK_CLOSE) { }

    terminal_close();
    return 0;
}

With that, go ahead and type make in your terminal. You should know have a copy of your platform-specific BearLibTerminal library and a new executable called rtut on Linux or rtut.exe on Windows. Go ahead and run it, and you should see the following:

Congrats, you’ve got your barebones C roguelike started! In the next chapter, we will actually figure out how to use BearLibTerminal and get our own @ moving around on the screen.