Leaving his mother’s office, Leo felt the weight of their conversation pressing down on him. His mind was spinning with everything she had said—about discipline, about the war, about his training. He knew she was right, but he couldn’t shake the frustration bubbling inside him. He needed to clear his head, to focus on something else, something concrete.

Instead of returning to his room, he found himself walking toward the Bellardian section. The familiar sounds of typing, low murmurs of deep discussions, and the occasional outburst of laughter greeted him as he stepped into their common room. The air was thick with concentration, a world away from strategy meetings and battlefield decisions.

He scanned the room, searching for Jeremy, hoping to immerse himself in something—anything—that would take his mind off everything else. Spotting someone he didn’t recognize, he walked over and asked, “Is Jeremy around?”

The man, a stocky programmer with glasses perched on the tip of his nose, looks up from his screen. “No, but can I help you?”

Leo hesitates for a second, then nods. “I’m looking for a C master.”

The man grins and extends his hand. “You found one. Name’s Edwin. Have a seat.”

Leo sits down, pulling out his laptop. Edwin gestures for him to show his latest work. As he skims through the code, his expression shifts from casual curiosity to amusement. “Ah, I see. You’ve come pretty far already. Loops, pointers, arrays—you’ve covered a lot.”

Leo waits, sensing there’s a ‘but’ coming.

“But,” Edwin continues, “you’re missing a key concept. The notion of a record.”

Leo frowns. “A record?”

Edwin leans back. “Yep. You see, up until now, you’ve been working with arrays. Arrays are great, but they only store one type of data at a time. A list of numbers, a list of strings, a list of pointers. But what if you want to group different kinds of data together?”

Leo thinks for a moment. “Like… if I wanted to store a player’s name and their score?”

“Exactly,” Edwin says, snapping his fingers. “Right now, you’d probably use two separate arrays—one for names and one for scores. Then, you’d use an index to match them up.”

Leo nods. “Yeah, that’s what I’d do.”

“Well, that’s a fine approach, and it’s actually how databases work,” Edwin admits. “But in C, there’s a better way if you want all that information to be stored together in memory.”

Leo tilts his head. “How?”

Edwin’s grin widens. “With a struct. It lets you define a new type that holds multiple variables of different types. Check this out.”

He types:

typedef struct {
    char* name;
    int points;
} user_t;

Leo studies the code. “So… typedef struct lets me create a custom type?”

“Bingo. And user_t is just the name we’re giving it. Now, instead of keeping separate lists for names and scores, we can store them together in one neat package.”

Leo nods slowly. “And how do I actually use it?”

Edwin types another snippet:

user_t player;
player.name = "Leo";
player.points = 100;

Leo’s eyes light up. “Oh, so now player is a single variable that holds both the name and the score!”

“Exactly,” Edwin confirms. “And if you wanted a list of players, like for a leaderboard, you could do this:”

user_t players[3];
players[0].name = "Leo";
players[0].points = 100;
players[1].name = "Jasmina";
players[1].points = 120;
players[2].name = "Sebastian";
players[2].points = 90;

Leo stares at the screen, his mind racing. “This makes organizing data so much easier.”

“Right?” Edwin chuckles. “It’s a game-changer. Now, let’s write a program that actually does something with this. You’re going to love this next part.”

Edwin leans forward, clearly enjoying the lesson. “Alright, now that you understand structs, let’s talk about how to use them in functions. There are two ways you can pass a struct to a function: by value or by pointer. Let me show you the difference.”

He types on his laptop:

void reset_score_by_value(user_t player) {
    player.points = 0;
}

Leo studies the code. “That looks just like any normal function parameter. But wait… you said ‘by value.’ Does that mean this function gets a copy of player?”

“Exactly,” Edwin says. “When you pass a struct this way, C makes a full copy of the data. So if you call this function, it won’t actually modify the original player in main.”

He types out another example:

void reset_score_by_pointer(user_t* player) {
    (*player).points = 0;
}

Leo frowns. “Wait… what’s with the parentheses?”

“Good question,” Edwin says. “Since player is a pointer, we first have to dereference it using *player to access the actual struct it points to. Then we can access its points field. But because the . operator has higher precedence than *, we need parentheses around *player before using ..”

Leo nods slowly. “So (*player).points = 0; means ‘follow the pointer, get the struct, then access points.’”

“Exactly,” Edwin confirms. “But there’s a more convenient way to write it. Instead of (*player).points, we can use player->points.”

He types:

void reset_score_by_pointer(user_t* player) {
    player->points = 0;
}

Leo raises an eyebrow. “That’s just shorthand?”

“Yep,” Edwin says. “The -> operator is syntactic sugar. It does the same thing as (*player).points, but it’s easier to read and write. When you see player->points, just remember that it means ‘follow the pointer and access points.’”

Leo reads through the code. “So if I call reset_score_by_value, it won’t actually change anything in main, but if I call reset_score_by_pointer, the change will stick?”

“Exactly,” Edwin confirms. “That’s why in most cases, you want to pass structs by pointer—especially if they’re large. Copying them over and over again wastes memory and slows things down.”

Leo nods. “Makes sense. Just like arrays—we don’t copy them, we pass a pointer.”

“Now you’ve got it,” Edwin says. “And that’s why you’ll see -> all the time when working with structs—because we usually deal with them as pointers.”

After a moment, Edwin leaned forward with a grin.

“Want to have some fun?”

Leo looked up. “What kind of fun?”

Edwin smirked. “How about writing your first video game?”

Leo’s eyes lit up. “Wait… really? You think I can?”

“Of course. I’ll walk you through it. Open your terminal: http://closedsourcebook.com/terminal.html (or docker run -it bellardian) and create a file called snake.c using emacs snake.c.”

“Good. Now, let’s go through this line by line so you understand how it works.”

Edwin pointed at the first section of code.

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <termios.h>
#include <fcntl.h>
#include <time.h>

“These lines bring in external functions we need. stdio.h is for input and output, like printf. stdlib.h gives us utilities like rand(). unistd.h lets us use system functions like usleep(), which will control the game speed. And time.h lets us work with the system clock to generate random numbers.”

Leo nodded. “Alright, so these are like tools the program needs?”

“Now, let’s define some important types.”

typedef struct {
  int x, y;
} Point;

“This creates a new type called Point. It’s just a structure holding an x and a y coordinate. We’ll use this for the snake’s body and the food position.”

“Okay, makes sense.”

“Now, let’s define the game state.”

typedef struct {
  Point snake[100]; // The snake's body segments
  int length;    // Snake length
  int dx, dy;    // Direction
  Point food;    // Food position
  int score;    // Player's score
  struct termios orig_termios; // Settings
  int width;    // Board width
  int height;    // Board height
} GameState;

“This structure stores everything about the game—the snake, food, score, movement direction, and even the terminal settings.”

“Next, your terminal normally operates line by line, while we are playing the game, we want to disable that behavior. The following two functions handle that. This stuff is a bit advanced, don’t worry about it, just copy it into your file.”

void disable_raw_mode(GameState *game) {
  tcsetattr(STDIN_FILENO, TCSAFLUSH, &game->orig_termios);
}

void enable_raw_mode(GameState *game) {
  struct termios raw;
  tcgetattr(STDIN_FILENO, &game->orig_termios);
  atexit((void (*)(void))disable_raw_mode);
  raw = game->orig_termios;
  raw.c_lflag &= ~(ECHO | ICANON);
  tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw);
}

“Normally, terminals wait for you to press Enter before processing input. We need instant reactions for our game, so we modify how input works. Again this is not very interesting stuff, just copy this function without worrying about it.”

Leo nodded. “Alright, got it.”

int kbhit() {
  struct termios oldt, newt;
  int ch;
  int oldf;
  tcgetattr(STDIN_FILENO, &oldt);
  newt = oldt;
  newt.c_lflag &= ~(ICANON | ECHO);
  tcsetattr(STDIN_FILENO, TCSANOW, &newt);
  oldf = fcntl(STDIN_FILENO, F_GETFL, 0);
  fcntl(STDIN_FILENO, F_SETFL, oldf | O_NONBLOCK);
  ch = getchar();
  tcsetattr(STDIN_FILENO, TCSANOW, &oldt);
  fcntl(STDIN_FILENO, F_SETFL, oldf);
  if (ch != EOF) {
    ungetc(ch, stdin);
    return 1;
  }
  return 0;
}

char get_key() {
  if (kbhit()) {
    return getchar();
  }
  return 0;
}

“Now, let’s initialize the game.”

void init_game(GameState *game) {
  game->width = 40;
  game->height = 20;
  enable_raw_mode(game);
  srand(time(NULL));
  game->length = 3;
  game->dx = 1;
  game->dy = 0;
  game->score = 0;
  
  game->snake[0].x = game->width / 2;
  game->snake[0].y = game->height / 2;
  int i = 1;
  while (i < game->length) {
    game->snake[i].x = game->snake[0].x - i;
    game->snake[i].y = game->snake[0].y;
    i++;
  }
  
  game->food.x = rand() % game->width;
  game->food.y = rand() % game->height;
}

Leo examined the function. “So this places the snake in the middle and randomly positions the food?”

“Exactly. Now let’s make it so we can see the game.”

void draw_game(const GameState *game) {
  system("clear");
  
  char screen[game->height][game->width];
  int i = 0;
  while (i < game->height) {
    int j = 0;
    while (j < game->width) {
      screen[i][j] = ' ';
      j++;
    }
    i++;
  }
  
  screen[game->food.y][game->food.x] = '*';
  i = 0;
  while (i < game->length) {
    screen[game->snake[i].y][game->snake[i].x] = (i == 0) ? 'O' : 'o';
    i++;
  }
  
  i = 0;
  while (i < game->height) {
    int j = 0;
    while (j < game->width) {
      putchar(screen[i][j]);
      j++;
    }
    putchar('\n');
    i++;
  }
  printf("Score: %d\n", game->score);
}

“So this function prints the game board?”

“Yep. It fills a grid with empty spaces, places the food, places the snake, and then prints everything to the screen.”

“Alright, what’s next?”

“Next, let’s make the snake move.”

void update_snake(GameState *game) {
  int i = game->length - 1;
  while (i > 0) {
    game->snake[i] = game->snake[i - 1];
    i--;
  }
  game->snake[0].x += game->dx;
  game->snake[0].y += game->dy;
  
  if (game->snake[0].x == game->food.x
      && game->snake[0].y == game->food.y
  ) {
    if (game->length < 100) {
      game->length++;
    }
    game->food.x = rand() % game->width;
    game->food.y = rand() % game->height;
    game->score++;
  }
}

Leo followed along. “So the head moves first, and the body follows?”

“Exactly. And if the snake eats the food, it grows.”

“How do we know if we lost?”

“That’s handled by this function.”

int check_collision(const GameState *game) {
  if (
    game->snake[0].x < 0
    || game->snake[0].x >= game->width
    || game->snake[0].y < 0
    || game->snake[0].y >= game->height
  )
    return 1;
  int i = 1;
  while (i < game->length) {
    if (
      game->snake[0].x == game->snake[i].x
      && game->snake[0].y == game->snake[i].y
    )
      return 1;
    i++;
  }
  return 0;
}

Leo frowned. “So if it hits the wall or itself, the game ends?”

“Yep.”

“Now let’s handle input.”

void handle_input(GameState *game) {
  char ch = get_key();
  if (
    (ch == 'w' || ch == 'W')
    && game->dy == 0
  ) {
    game->dx = 0;
    game->dy = -1;
  }
  else if (
    (ch == 's' || ch == 'S')
    && game->dy == 0
  ) {
    game->dx = 0;
    game->dy = 1;
  }
  else if (
    (ch == 'a' || ch == 'A')
    && game->dx == 0
  ) {
    game->dx = -1;
    game->dy = 0;
  }
  else if (
    (ch == 'd' || ch == 'D')
    && game->dx == 0
  ) {
    game->dx = 1;
    game->dy = 0;
  }
}

Leo smiled. “So this lets me control the snake with W, A, S, and D?”

“Exactly!”

“Finally, the main function, where the program starts.”

int main() {
  GameState game;
  init_game(&game);

  while (1) {
    handle_input(&game);
    update_snake(&game);
    if (check_collision(&game)) break;
    draw_game(&game);
    usleep(100000);
  }

  disable_raw_mode(&game);
  printf("Game Over! Score: %d\n", game.score);
  return 0;
}

“Now, compile and run it!”

Leo typed:

gcc snake.c -o snake && ./snake

The game appeared. He moved the snake around and grinned. “I made a game!”


Leo walked away from the Bellardian section of the Senate, still feeling the rush of excitement from writing his first video game. The logic, the movement, the challenge of making everything work—it had been exhilarating. As he reached his room, he saw Jenny, Sebastian, and Jasmina waiting for him.

Jenny smiled. “Hey, how are you doing?”

Leo grinned. “I just wrote my first video game,” he said, still energized. “A snake game, all in C.”

Jasmina’s eyes lit up. “That’s amazing! You must be really getting the hang of it.”

Sebastian nodded approvingly. “A video game, huh? Looks like you’re becoming a real programmer.”

They all laughed, and for a brief moment, the weight of everything they had been through seemed lighter. But before they could continue talking, the door opened, and Brielle stepped inside.

Leo immediately turned serious. “Do you have any news about Elias?”

Brielle sighed. “He’s stable, but his condition is still difficult.”

Their expressions darkened. They had risked so much to get him back, and now all they could do was wait.

Brielle reached into her bag and pulled out a set of small booklets. “Alright, you four,” she said, tossing a passport to each of them.

Leo caught his and flipped through the pages. “What is this?”

“Your new identities,” Brielle said.

Jenny looked up, confused. “Why do we need new identities?”

“For your safety,” Brielle explained. “You’re being sent far away to finish your training. You need to pack your bags.”

Sebastian glanced at her, wary. “Where are we going?”

Brielle looked at each of them before answering. “Paris. In France.”