Learning NCurses with C

Posted on Tue 25 March 2025 in posts

Introduction

C is a wonderful language. It is a go-to when it comes to lower level programming. It has been instrumental in developing most of modern software (Linux, Python language, etc.)

In my continued learning about the language, I decided to try learning about ncurses, a library for writing terminal user interfaces.

I also wanted it to be fun. So naturally, I decided to create a small game.

Choice of Game

In the spirit of retro gaming and using what some might consider a "retro language" (I think I saw such designation for C somewhere on the internet but I am not too sure where), I decided to simply write a version of the Space Invaders game.

The game itself is simple: we have a player's character moving side-to-side on the screen while attempting to eliminate most of the enemies. The enemies approach slowly from the top part of the screen crawling down to bottom.

Results

screenshot

The resulting game is very simple and could probably benefit from more optimizations.

But I learned a lot in the process of working with ncurses! Here is the main loop of the game!

int main(void) {
  enemy enemies[ENEMY_COUNT];
  initscr();            // initialize ncurses
  keypad(stdscr, TRUE); // Enable special keys
  noecho();             // Don't show typed characters
  curs_set(0);
  refresh();
  for (int i = 0; i < ENEMY_COUNT; i = i + 1) {
    enemies[i].x_coord =
        COLS / 2 -
        (ENEMY_SIZE + 1) * (ENEMY_COUNT / 2 - i); //+1 is because of space
    enemies[i].y_coord = 1;
    enemies[i].is_defeated = 0;
  }

  int x_main = COLS / 2;
  int y_main = LINES - 2;

  while (1) {
    KEY_PRESSED = 0;
    mvprintw(y_main, x_main, CHARACTER);
    int x_bullet = x_main;
    int y_bullet = y_main - 1;

    for (int i = 0; i < ENEMY_COUNT; i++) {
      // display enemies
      if (!enemies[i].is_defeated) {
        mvprintw(enemies[i].y_coord, enemies[i].x_coord, ENEMY);
      }
    }
    input = getch();
    if (input == 'q') {
      break;
    }
    switch (input) {

    case KEY_LEFT:
      KEY_PRESSED = 1;
      if (x_main > 1) {
        mvprintw(y_main, x_main, "    ");
        x_main -= 2;
      }
      break;
    case KEY_RIGHT:
      KEY_PRESSED = 1;
      if (x_main < COLS - 4) {
        mvprintw(y_main, x_main, "    ");
        x_main += 2;
      }
      break;
    case ' ':

      while (y_bullet > 1) {
        mvprintw(y_bullet, x_bullet, BULLET);
        refresh();
        delay_ms(100);
        mvprintw(y_bullet, x_bullet, " ");
        refresh();
        y_bullet -= 1;
        for (int i = 0; i < ENEMY_COUNT; i++) {
          if (enemies[i].y_coord == y_bullet &&
              x_bullet >= enemies[i].x_coord &&
              x_bullet <= enemies[i].x_coord + ENEMY_SIZE) {
            enemies[i].is_defeated = 1;
            mvprintw(enemies[i].y_coord, enemies[i].x_coord, "    ");
            break;
          }
        }
      }
      mvprintw(y_bullet, x_bullet, " ");
    }
    // move enemies down
    if (!KEY_PRESSED) {
      for (int i = 0; i < ENEMY_COUNT; i = i + 1) {
        if (!enemies[i].is_defeated) {
          mvprintw(enemies[i].y_coord, enemies[i].x_coord, "    ");
          enemies[i].y_coord = enemies[i].y_coord + 1;
          mvprintw(enemies[i].y_coord, enemies[i].x_coord, ENEMY);
        }
      }
    }
  }
  endwin();
  return 0;
}