thecodingidiot.com

Who Wants to Be a Game Developer?Setup

Setup

No new tools are needed for this chapter. The toolchain from f05/00 covers everything: gcc, make, valgrind.

Project structure

g01a is the first chapter that is not a library — it is a program that depends on one. The project has a libtci/ subdirectory that carries the full libtci and libtciutil source:

g01a-practice/
├── libtci/
│   ├── Makefile
│   ├── libtci.h
│   ├── libtciutil.h
│   ├── tci_bzero.c
│   └── ... (all source files)
├── Makefile
├── game.h
├── main.c
├── load.c
├── display.c
└── game.c

The top-level Makefile drives the build. $(MAKE) -C libtci descends into the subdirectory and runs its own Makefile to produce libtci.a and libtciutil.a. The game then links against both.

This is the standard pattern for a C project with a local library dependency — the same mechanism used by larger projects that vendor a third-party library alongside their own code.

Clone the companion repo to get everything in place:

git clone https://github.com/thecodingidiot-com/g01a-the-developer.git g01a-practice
cd g01a-practice

Build the libraries and confirm there are no warnings:

make -C libtci re

libtci.a and libtciutil.a should appear inside libtci/ with no warnings. You only need to do this once — subsequent make re calls at the top level build the library only if its sources have changed.


Create the game files

Create four empty source files and the shared header:

touch main.c load.c display.c game.c game.h

Create a questions.txt placeholder — the real questions come in the final page:

touch questions.txt

Write game.h

game.h is the shared header for all four source files. It defines question_t and declares every function across the modules:

#ifndef GAME_H
#define GAME_H
 
#include "libtci.h"
#include "libtciutil.h"
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <time.h>
 
#define LEVELS  15
 
typedef struct {
    char    *text;
    char    *opts[4];
    int      answer;
    char    *hint;
} question_t;
 
/* load.c */
question_t  **load_questions(const char *path, int *count);
void          free_questions(question_t **questions, int count);
 
/* display.c */
void    display_ladder(int level, int safe_level);
void    display_question(question_t *q, int hidden[4]);
void    display_audience(question_t *q);
void    display_win(void);
void    display_loss(int safe_level);
void    display_walkaway(int level);
 
/* game.c */
void    game_loop(question_t **questions, int count);
 
#endif

Write the Makefile

The game compiles four source files and links the two libraries:

NAME    = game
CC      = gcc
CFLAGS  = -Wall -Wextra -g -std=c99 -I libtci
SRCS    = main.c load.c display.c game.c
OBJS    = $(SRCS:.c=.o)
LIBS    = libtci/libtci.a libtci/libtciutil.a
 
.PHONY: all clean fclean re
 
all: $(NAME)
 
$(NAME): $(OBJS) $(LIBS)
	$(CC) $(CFLAGS) $(OBJS) $(LIBS) -o $(NAME)
 
$(LIBS):
	$(MAKE) -C libtci
 
%.o: %.c game.h
	$(CC) $(CFLAGS) -c $< -o $@
 
clean:
	$(MAKE) -C libtci clean
	rm -f $(OBJS)
 
fclean: clean
	$(MAKE) -C libtci fclean
	rm -f $(NAME)
 
re: fclean all

-I libtci tells the compiler to search libtci/ for headers, so #include "libtci.h" in game.h resolves to libtci/libtci.h without changing the include directive. $(LIBS) lists the archives by path; Make builds them by descending into libtci/ when they do not yet exist. clean and fclean propagate into the subdirectory so make fclean leaves the tree completely empty.

The %.o: %.c game.h rule rebuilds every object if game.h changes.


Add stubs

Paste a minimal stub into each source file so the project compiles from the start.

main.c:

#include "game.h"
 
int main(int argc, char **argv)
{
    (void)argc;
    (void)argv;
    return (0);
}

load.c:

#include "game.h"
 
question_t  **load_questions(const char *path, int *count)
{
    (void)path;
    *count = 0;
    return (NULL);
}
 
void    free_questions(question_t **questions, int count)
{
    (void)questions;
    (void)count;
}

display.c:

#include "game.h"
 
void    display_ladder(int level, int safe_level)
{
    (void)level;
    (void)safe_level;
}
void    display_question(question_t *q, int hidden[4])
{
    (void)q;
    (void)hidden;
}
void    display_audience(question_t *q)
{
    (void)q;
}
void    display_win(void)
{
}
void    display_loss(int safe_level)
{
    (void)safe_level;
}
void    display_walkaway(int level)
{
    (void)level;
}

game.c:

#include "game.h"
 
void    game_loop(question_t **questions, int count)
{
    (void)questions;
    (void)count;
}

Compile:

make re

Zero warnings. The binary does nothing yet — that changes page by page.


Get the tester

git clone https://github.com/thecodingidiot-com/g01a-the-developer.git
cp g01a-the-developer/test.sh ~/g01a-practice/

Leave the clone in place. You will run bash test.sh after the final page.


What the finished game looks like

When the implementation is complete, a run looks like this:

    15: £1,000,000
    14: £500,000
    13: £250,000
    12: £125,000
    11: £64,000
  * 10: £32,000
     9: £16,000
     8: £8,000
     7: £4,000
     6: £2,000
  *  5: £1,000
     4: £500
     3: £300
     2: £200
  >> 1: £100
 
Which command lists files and directories in a Unix terminal?
 
  A. ls
  B. dir
  C. list
  D. show
 
[1] 50:50  [2] Phone  [3] Audience  [W] Walk away
Your answer (A-D): 

The >> marker and yellow highlight are the same level. The * marks safe levels. The game renders this with tci_printf — no other output function is used anywhere in the display code.