UP | HOME

Full Game
COP-3223H

Table of Contents

notes

  • overwrite_mode
  • converting ascii art into c arrays (script)
  • #include
  • txt -> c array generator python script

    wget https://www.cs.ucf.edu/~gazzillo/teaching/cop3223hspring26/livedemos/full_game/background.py
    python3 background.py < background.array > background.c
    

Non-canonical input

noncanon.c

// https://sourceware.org/glibc/manual/latest/html_mono/libc.html#Noncanon-Example

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

/* Use this variable to remember original terminal attributes. */

struct termios saved_attributes;

void
reset_input_mode (void)
{
  tcsetattr (STDIN_FILENO, TCSANOW, &saved_attributes);
}

void
set_input_mode (void)
{
  struct termios tattr;

  /* Make sure stdin is a terminal. */
  if (!isatty (STDIN_FILENO))
    {
      fprintf (stderr, "Not a terminal.\n");
      exit (EXIT_FAILURE);
    }

  /* Save the terminal attributes so we can restore them later. */
  tcgetattr (STDIN_FILENO, &saved_attributes);
  atexit (reset_input_mode);

  /* Set the funny terminal modes. */
  tcgetattr (STDIN_FILENO, &tattr);
  tattr.c_lflag &= ~(ICANON|ECHO); /* Clear ICANON and ECHO. */
  tattr.c_cc[VMIN] = 1;
  tattr.c_cc[VTIME] = 0;
  tcsetattr (STDIN_FILENO, TCSAFLUSH, &tattr);
}

int
main (void)
{
  char c;

  set_input_mode ();

  while (1)
    {
      read (STDIN_FILENO, &c, 1);
      if (c == '\004')          /* C-d */
        break;
      else
        write (STDOUT_FILENO, &c, 1);
    }

  return EXIT_SUCCESS;
}

Example: rot13

// https://sourceware.org/glibc/manual/latest/html_mono/libc.html#Noncanon-Example

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

/* Use this variable to remember original terminal attributes. */

struct termios saved_attributes;

void
reset_input_mode (void)
{
  tcsetattr (STDIN_FILENO, TCSANOW, &saved_attributes);
}

void
set_input_mode (void)
{
  struct termios tattr;

  /* Make sure stdin is a terminal. */
  if (!isatty (STDIN_FILENO))
    {
      fprintf (stderr, "Not a terminal.\n");
      exit (EXIT_FAILURE);
    }

  /* Save the terminal attributes so we can restore them later. */
  tcgetattr (STDIN_FILENO, &saved_attributes);
  atexit (reset_input_mode);

  /* Set the funny terminal modes. */
  tcgetattr (STDIN_FILENO, &tattr);
  tattr.c_lflag &= ~(ICANON|ECHO); /* Clear ICANON and ECHO. */
  tattr.c_cc[VMIN] = 1;
  tattr.c_cc[VTIME] = 0;
  tcsetattr (STDIN_FILENO, TCSAFLUSH, &tattr);
}

int
main (void)
{
  char c;

  set_input_mode ();

  while (1)
    {
      read (STDIN_FILENO, &c, 1);
      if ('A' <= c && c <= 'Z') {
        c = (((c - 'A') + 13) % ('Z' - 'A' + 1)) + 'A';
      }
      if ('a' <= c && c <= 'z') {
        c = (((c - 'a') + 13) % ('z' - 'a' + 1)) + 'a';
      }
      if (c == '\004')          /* C-d */
        break;
      else
        write (STDOUT_FILENO, &c, 1);
    }

  return EXIT_SUCCESS;
}

State machines

chase.c

// https://sourceware.org/glibc/manual/latest/html_mono/libc.html#Noncanon-Example

#include <stdio.h>
#include <stdbool.h>

#include "term.c"

int main(int argc, char **argv) {
  set_input_mode ();
  printf("\033[?25l"); // hide cursor

  int width = 40;
  int height = 20;

  // player data
  //       0            1         2
  enum { vulnerable, invincible, dead };
  int player_state = vulnerable;
  int player_x = width / 2;
  int player_y = height / 2;

  // enemy data
  int enemy_x = 1;
  int enemy_y = 1;
  enum { chasing, running, defeated };
  int enemy_state = chasing;

  // potion data
  int potion_x = 29;
  int potion_y = 11;
  int random = 1;

  enum { available, using, done };
  int potion_state = available;
  int potion_counter = 0;

  while (1) {
    printf("\x1b[2J");
    printf("\x1b[H");
    // 0 available
    // 1 using
    // 2 done

    // initial state is 0
    // switch (potion_state) {
    //   case 0:
    // }
    switch (potion_state) {
    case available:
      printf("\033[%d;%dH", potion_y, potion_x);
      printf("/\\");
      printf("\033[%d;%dH", potion_y + 1, potion_x);
      printf("\\/");
      break;
    case using:
      // intentionally empty
      break;
    }

    printf("\033[%d;%dH", enemy_y, enemy_x);
    switch (enemy_state) {
    case chasing: // case 0:
      putchar('@');
      break;
    case running: // case 1:
      putchar('@');
      break;
    case defeated: // case 2:
      putchar('X');
    }

    printf("\033[%d;%dH", player_y, player_x);
    switch (player_state) {
    case vulnerable:
      putchar('h');
      break;
    case invincible:
      putchar('H');
      break;
    case dead:
      printf(":(");
    }

    /* // debugging output */
    /* printf("\033[%d;%dH", height + 1, 0); */
    /* printf("player_y: %d\n", player_y); */
    /* printf("player_x: %d\n", player_x); */
    /* printf("enemy_y: %d\n", enemy_y); */
    /* printf("enemy_x: %d\n", enemy_x); */
    /* printf("potion_y: %d\n", potion_y); */
    /* printf("potion_x: %d\n", potion_x); */

    // read input

    int direction_x = 0;
    int direction_y = 0;
    bool valid = true;
    do {
      char c = '\0';
      scanf("%c", &c);
      switch (c) {
      case 'i':
        direction_y = -2;
        break;
      case 'k':
        direction_y = 2;
        break;
      case 'j':
        direction_x = -2;
        break;
      case 'l':
        direction_x = 2;
        break;
      default:
        valid = false;
        break;
      }
    } while (! valid);

    switch(player_state) {
    case vulnerable:   // case 0:
      player_x += direction_x;
      player_y += direction_y;
      break;
    case invincible:  // case 1:
      player_x += direction_x;
      player_y += direction_y;
      if (enemy_x - 1 <= player_x && player_x <= enemy_x + 1 &&
          enemy_y - 1 <= player_y && player_y <= enemy_y + 1) {
        player_state = vulnerable;
        enemy_state = defeated;
        potion_state = done;
      }
      break;
    }

    switch (enemy_state) {
    case chasing: // chasing
      if (player_x > enemy_x) {
        enemy_x += 1;
      }
      if (player_x < enemy_x) {
        enemy_x -= 1;
      }
      if (player_y > enemy_y) {
        enemy_y += 1;
      }
      if (player_y < enemy_y) {
        enemy_y -= 1;
      }

      if ((player_x == enemy_x || player_x == enemy_x + 1) &&
          (player_y == enemy_y || player_y == enemy_y + 1)) {
        player_state = dead;
      }
      break;
    case running:
      if (player_x > enemy_x) {
        enemy_x -= 1;
      }
      if (player_x < enemy_x) {
        enemy_x += 1;
      }
      if (player_y > enemy_y) {
        enemy_y -= 1;
      }
      if (player_y < enemy_y) {
        enemy_y += 1;
      }
      if (enemy_x < 0) {
        enemy_x = 0;
      }
      if (enemy_x > width) {
        enemy_x = width;
      }
      if (enemy_y < 0) {
        enemy_y = 0;
      }
      if (enemy_y > height) {
        enemy_y = height;
      }
      break;
    }

    switch (potion_state) {
    case available:
      if ((player_x == potion_x || player_x == potion_x + 1) &&
          (player_y == potion_y || player_y == potion_y + 1)) {
        player_state = invincible;
        enemy_state = running;
        potion_state = using;
        potion_counter = 10;
      }
      break;
    case using:
      potion_counter -= 1;
      if (potion_counter == 1) {
        player_state = vulnerable;
        enemy_state = chasing;
        potion_state = available;
        random = (random * 97) % 65533;
        potion_x = random % width;
        random = (random * 97) % 65533;
        potion_y = random % height;
      }
      break;
    }      
  }
}

term.c

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdbool.h>
#include <termios.h>

// https://sourceware.org/glibc/manual/latest/html_mono/libc.html#Noncanon-Example

/* Use this variable to remember original terminal attributes. */

struct termios saved_attributes;

void
reset_input_mode (void)
{
  tcsetattr (STDIN_FILENO, TCSANOW, &saved_attributes);
}

void
set_input_mode (void)
{
  struct termios tattr;

  /* Make sure stdin is a terminal. */
  if (!isatty (STDIN_FILENO))
    {
      fprintf (stderr, "Not a terminal.\n");
      exit (EXIT_FAILURE);
    }

  /* Save the terminal attributes so we can restore them later. */
  tcgetattr (STDIN_FILENO, &saved_attributes);
  atexit (reset_input_mode);

  /* Set the funny terminal modes. */
  tcgetattr (STDIN_FILENO, &tattr);
  tattr.c_lflag &= ~(ICANON|ECHO); /* Clear ICANON and ECHO. */
  tattr.c_cc[VMIN] = 1;
  tattr.c_cc[VTIME] = 0;
  tcsetattr (STDIN_FILENO, TCSAFLUSH, &tattr);
}

// https://stackoverflow.com/questions/448944/c-non-blocking-keyboard-input

bool kbhit() {
  struct timeval tv = { 0L, 0L };
  fd_set fds;
  FD_ZERO(&fds);
  FD_SET(0, &fds);
  if (select(1, &fds, NULL, NULL, &tv) == 1) {
    return true;
  } else {
    return false;
  }
}

int getch() {
  int r;
  unsigned char c;
  do {
    if ((r = read(0, &c, sizeof(c))) < 0) {
      return '\0';  // null character on error
    }
  } while (r < 1);  // ensures read returns one character
  return c;
}

Sprite Arrays

Example ascii art

/\
\/

Example array

int potion_sprite_width = 2;
int potion_sprite_height = 2;
char potion_sprite[2][2] = { { '/', '\\' },
                             { '\\', '/' } }

Another example array

int potion_sprite_width = 2;
int potion_sprite_height = 3;
char potion_sprite[3][2] = { { 'w', 'w' },
                             { '|', '|' },
                             { '/', '\\' } };

Drawing the array

for (int j = 0; j < potion_sprite_height; j++) {
  printf("\033[%d;%dH", potion_y + j, potion_x);
  for (int i = 0; i < potion_sprite_width; i++) {
    putchar(potion_sprite[j][i]);
  }
 }

In-class coding

/* int potion_sprite_width = 2; */
/* int potion_sprite_height = 2; */

/* // y x */
/* // 0 0 */
/* // 0 1 */
/* // 1 0 */
/* // 1 1 */
/* char potion_sprite[2][2] = { { '/', '\\' }, */
/* 														 { '\\', '/' } }; */



int potion_sprite_width = 2;
int potion_sprite_height = 3;
char potion_sprite[3][2] = { { 'w', 'w' },
                             { '|', '|' },
                             { '/', '\\' } };


/*
  escape characters
  backlash is the escape character

  \n
  \0134

*/

int sprite_x = 10;
int sprite_y = 30;
// how we move to y, x coordinates
// j, i position within the array
// y, x position on the screen
// 0, 1
for (int j = 0; j < potion_sprite_height; j++) {
  // 0, 1
  for (int i = 0; i < potion_sprite_width; i++) {
    int x = sprite_x + i;
    int y = sprite_y + j;
    // positioning the cursor
    printf("\033[%d;%dH", y, x);
    /* printing each y, x element of the array */
    putchar(potion_sprite[j][i]);
  }
 }

// y x
// 1 1
// 1 2
// 2 1
// 2 2

printf("\n");
exit(1);

Chase

// https://sourceware.org/glibc/manual/latest/html_mono/libc.html#Noncanon-Example

#include <stdio.h>
#include <stdbool.h>

#include "term.c"

int main(int argc, char **argv) {
  set_input_mode ();
  printf("\033[?25l"); // hide cursor

  /* int potion_sprite_width = 2; */
  /* int potion_sprite_height = 2; */
  /* char potion_sprite[2][2] = { { '/', '\\' }, */
  /* 														 /* { '\\', '/' } }; *\/ */
  /* int potion_sprite_width = 4; */
  /* int potion_sprite_height = 3; */
  /* char potion_sprite[4][3] = { { 0x6C, 0x6B }, */
  /* 														 { 0x6D, 0x6A } }; */
  int potion_sprite_width = 2;
  int potion_sprite_height = 3;
  char potion_sprite[3][2] = { { 'w', 'w' },
                               { '|', '|' },
                               { '/', '\\' } };

  int width = 40;
  int height = 20;

  // player data
  //       0            1         2
  enum { vulnerable, invincible, dead };
  int player_state = vulnerable;
  int player_x = width / 2;
  int player_y = height / 2;

  // enemy data
  int enemy_x = 1;
  int enemy_y = 1;
  enum { chasing, running, defeated };
  int enemy_state = chasing;

  // potion data
  int potion_x = 29;
  int potion_y = 11;
  int random = 1;

  enum { available, using, done };
  int potion_state = available;
  int potion_counter = 0;

  while (1) {
    printf("\x1b[2J");
    printf("\x1b[H");

    switch (potion_state) {
    case available:
      /* printf("\e(0"); */
      for (int j = 0; j < potion_sprite_height; j++) {
        printf("\033[%d;%dH", potion_y + j, potion_x);
        for (int i = 0; i < potion_sprite_width; i++) {
          putchar(potion_sprite[j][i]);
        }
      }
      /* printf("\e(B\n"); */
      /* printf("\033[%d;%dH", potion_y, potion_x); */
      /* printf("/\\"); */
      /* printf("\033[%d;%dH", potion_y + 1, potion_x); */
      /* printf("\\/"); */
      break;
    case using:
      // intentionally empty
      break;
    }

    printf("\033[%d;%dH", enemy_y, enemy_x);
    switch (enemy_state) {
    case chasing: // case 0:
      putchar('@');
      break;
    case running: // case 1:
      putchar('@');
      break;
    case defeated: // case 2:
      putchar('X');
    }

    printf("\033[%d;%dH", player_y, player_x);
    switch (player_state) {
    case vulnerable:
      putchar('h');
      break;
    case invincible:
      putchar('H');
      break;
    case dead:
      printf(":(");
    }

    /* // debugging output */
    /* printf("\033[%d;%dH", height + 1, 0); */
    /* printf("player_y: %d\n", player_y); */
    /* printf("player_x: %d\n", player_x); */
    /* printf("enemy_y: %d\n", enemy_y); */
    /* printf("enemy_x: %d\n", enemy_x); */
    /* printf("potion_y: %d\n", potion_y); */
    /* printf("potion_x: %d\n", potion_x); */

    // read input

    int direction_x = 0;
    int direction_y = 0;
    bool valid = true;
    do {
      char c = '\0';
      scanf("%c", &c);
      switch (c) {
      case 'i':
        direction_y = -2;
        break;
      case 'k':
        direction_y = 2;
        break;
      case 'j':
        direction_x = -2;
        break;
      case 'l':
        direction_x = 2;
        break;
      default:
        valid = false;
        break;
      }
    } while (! valid);

    switch(player_state) {
    case vulnerable:   // case 0:
      player_x += direction_x;
      player_y += direction_y;
      break;
    case invincible:  // case 1:
      player_x += direction_x;
      player_y += direction_y;
      if (enemy_x - 1 <= player_x && player_x <= enemy_x + 1 &&
          enemy_y - 1 <= player_y && player_y <= enemy_y + 1) {
        player_state = vulnerable;
        enemy_state = defeated;
        potion_state = done;
      }
      break;
    }

    switch (enemy_state) {
    case chasing: // chasing
      if (player_x > enemy_x) {
        enemy_x += 1;
      }
      if (player_x < enemy_x) {
        enemy_x -= 1;
      }
      if (player_y > enemy_y) {
        enemy_y += 1;
      }
      if (player_y < enemy_y) {
        enemy_y -= 1;
      }

      if ((player_x == enemy_x || player_x == enemy_x + 1) &&
          (player_y == enemy_y || player_y == enemy_y + 1)) {
        player_state = dead;
      }
      break;
    case running:
      if (player_x > enemy_x) {
        enemy_x -= 1;
      }
      if (player_x < enemy_x) {
        enemy_x += 1;
      }
      if (player_y > enemy_y) {
        enemy_y -= 1;
      }
      if (player_y < enemy_y) {
        enemy_y += 1;
      }
      if (enemy_x < 0) {
        enemy_x = 0;
      }
      if (enemy_x > width) {
        enemy_x = width;
      }
      if (enemy_y < 0) {
        enemy_y = 0;
      }
      if (enemy_y > height) {
        enemy_y = height;
      }
      break;
    }

    switch (potion_state) {
    case available:
      if ((player_x == potion_x || player_x == potion_x + 1) &&
          (player_y == potion_y || player_y == potion_y + 1)) {
        player_state = invincible;
        enemy_state = running;
        potion_state = using;
        potion_counter = 10;
      }
      break;
    case using:
      potion_counter -= 1;
      if (potion_counter == 1) {
        player_state = vulnerable;
        enemy_state = chasing;
        potion_state = available;
        random = (random * 97) % 65533;
        potion_x = random % width;
        random = (random * 97) % 65533;
        potion_y = random % height;
      }
      break;
    }      
  }
}

Background Arrays

Same idea as sprite arrays

  • txt -> c array generator python script

    wget https://www.cs.ucf.edu/~gazzillo/teaching/cop3223hspring26/livedemos/full_game/background.py
    python3 background.py < background.array > background.c
    

Example: larger screen

// https://sourceware.org/glibc/manual/latest/html_mono/libc.html#Noncanon-Example

#include <stdio.h>
#include <stdbool.h>

#include "term.c"

int main(int argc, char **argv) {
  set_input_mode ();
  printf("\033[?25l"); // hide cursor

  /* int potion_sprite_width = 2; */
  /* int potion_sprite_height = 2; */
  /* char potion_sprite[2][2] = { { '/', '\\' }, */
  /* 														 /* { '\\', '/' } }; *\/ */
  /* int potion_sprite_width = 4; */
  /* int potion_sprite_height = 3; */
  /* char potion_sprite[4][3] = { { 0x6C, 0x6B }, */
  /* 														 { 0x6D, 0x6A } }; */
  int potion_sprite_width = 2;
  int potion_sprite_height = 2;
  char potion_sprite[2][2] = { {  'w', 'w' },
                               { '\\', '/' } };

  int width = 40;
  int height = 20;

  int background_rows = 21;
  int background_cols = 41;
  char background[21][41] = {
    { ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ',},
    { ' ', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_',},
    { ' ','\\', '-', '-', '-', '-', '-', '-', '-', '-', '-', '-', '-', '-', '_', '_', '-', '-', '-', '-', '/', ' ', ' ', ' ','\\', '-', '-', '-', '-', '-', '-', '-', '-', '-', '-', '-', ' ', '_', '-', '-', '/',},
    { ' ', ' ','\\', '-', '-', '-', '-', '-', '-', '-', '-', '-', '-', '/', ' ', ' ','\\', '-', '-', '/', ' ', ' ', ' ', ' ', ' ','\\', '_', '_', '/','\\', '-', '-', '_', '_', '/','\\', '/', ' ','\\', '/', ' ',},
    { ' ', ' ', ' ','\\', '_', '_', '/','\\', '-', '-', '_', '_', '/', ' ', ' ', ' ', ' ','\\', '/', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ','\\', '/', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ',},
    { ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ','\\', '/', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ',},
    { ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', '_', '/','\\', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ',},
    { ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', '/', '-', '-', '-','\\', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ',},
    { ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', '|', '-', '-', '-', '/', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ',},
    { ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ','\\', '_', '_', '/', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ',},
    { ' ', ' ', ' ', ' ', ' ', '_', '_', '_', '_', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ',},
    { ' ', ' ', ' ', ' ', '/', '-', '-', '-', '-','\\', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ',},
    { ' ', ' ', ' ', ' ','\\', '-', '-', '-', '-', '/', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ',},
    { ' ', ' ', ' ', ' ', ' ', '|', '_', '_', '/', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ',},
    { ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', '_', '/','\\', ' ', ' ', ' ', ' ', ' ', '/','\\', '_', ' ', ' ', ' ', ' ',},
    { ' ', '_', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', '/', '-', '-', '-','\\', '_', '_', '_', '/', '-', '-', '-','\\', ' ', ' ', '_',},
    { ' ', '-','\\', ' ', ' ', ' ', ' ', '_', '_', ' ', ' ', '/','\\', ' ', ' ', ' ', ' ', ' ', ' ', '/','\\', ' ', ' ', '_', '/', '-', '-', '-', '-', '-', '-', '-', '-', '-', '-', '-', '-', '-','\\', '/', '-',},
    { ' ', '-', '-','\\', ' ', ' ', '/', '-', '-','\\', '/', '-', '-','\\', ' ', ' ', ' ', ' ', '/', '-', '-','\\', '/', '-', '-', '-', '-', '-', '-', '-', '-', '-', '-', '-', '-', '-', '-', '-', '-', '-', '-',},
    { ' ', '-', '-', '-','\\', '/', '-', '-', '-', '-', '-', '-', '-', '-','\\', ' ', ' ', '/', '-', '-', '-', '-', '-', '-', '-', '-', '-', '-', '-', '-', '-', '-', '-', '-', '-', '-', '-', '-', '-', '-', '-',},
    { ' ', '-', '-', '-', '-', '-', '-', '-', '-', '-', '-', '-', '-', '-', '-','\\', '/', '-', '-', '-', '-', '-', '-', '-', '-', '-', '-', '-', '-', '-', '-', '-', '-', '-', '-', '-', '-', '-', '-', '-', '-',},
    { ' ', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_',},
  };


  // player data
  //       0            1         2
  enum { vulnerable, invincible, dead };
  int player_state = vulnerable;
  int player_x = width / 2;
  int player_y = height / 2;

  // enemy data
  int enemy_x = 2;
  int enemy_y = 2;
  enum { chasing, running, defeated };
  int enemy_state = chasing;

  // potion data
  int potion_x = 29;
  int potion_y = 11;
  int random = 1;

  enum { available, using, done };
  int potion_state = available;
  int potion_counter = 0;

  while (1) {
    printf("\x1b[2J");
    printf("\x1b[H");

    // draw background
    background_rows = 21;
    background_cols = 41;
    background[21][41];
    for (int j = 1; j < background_rows; j++) {
      printf("\033[%d;%dH", j, 1);
      for (int i = 1; i < background_cols; i++) {
        putchar(background[j][i]);
      }
    }

    switch (potion_state) {
    case available:
      /* printf("\e(0"); */
      for (int j = 0; j < potion_sprite_height; j++) {
        printf("\033[%d;%dH", potion_y + j, potion_x);
        for (int i = 0; i < potion_sprite_width; i++) {
          putchar(potion_sprite[j][i]);
        }
      }
      /* printf("\e(B\n"); */
      /* printf("\033[%d;%dH", potion_y, potion_x); */
      /* printf("/\\"); */
      /* printf("\033[%d;%dH", potion_y + 1, potion_x); */
      /* printf("\\/"); */
      break;
    case using:
      // intentionally empty
      break;
    }

    printf("\033[%d;%dH", enemy_y, enemy_x);
    switch (enemy_state) {
    case chasing: // case 0:
      putchar('@');
      break;
    case running: // case 1:
      putchar('@');
      break;
    case defeated: // case 2:
      putchar('X');
    }

    printf("\033[%d;%dH", player_y, player_x);
    switch (player_state) {
    case vulnerable:
      putchar('h');
      break;
    case invincible:
      putchar('H');
      break;
    case dead:
      printf(":(");
    }

    /* // debugging output */
    /* printf("\033[%d;%dH", height + 1, 0); */
    /* printf("player_y: %d\n", player_y); */
    /* printf("player_x: %d\n", player_x); */
    /* printf("enemy_y: %d\n", enemy_y); */
    /* printf("enemy_x: %d\n", enemy_x); */
    /* printf("potion_y: %d\n", potion_y); */
    /* printf("potion_x: %d\n", potion_x); */

    // read input

    int direction_x = 0;
    int direction_y = 0;
    bool valid = true;
    do {
      char c = '\0';
      scanf("%c", &c);
      switch (c) {
      case 'i':
        direction_y = -2;
        break;
      case 'k':
        direction_y = 2;
        break;
      case 'j':
        direction_x = -2;
        break;
      case 'l':
        direction_x = 2;
        break;
      default:
        valid = false;
        break;
      }
    } while (! valid);

    switch(player_state) {
    case vulnerable:   // case 0:
      player_x += direction_x;
      player_y += direction_y;
      break;
    case invincible:  // case 1:
      player_x += direction_x;
      player_y += direction_y;
      if (enemy_x - 1 <= player_x && player_x <= enemy_x + 1 &&
          enemy_y - 1 <= player_y && player_y <= enemy_y + 1) {
        player_state = vulnerable;
        enemy_state = defeated;
        potion_state = done;
      }
      break;
    }

    switch (enemy_state) {
    case chasing: // chasing
      if (player_x > enemy_x) {
        enemy_x += 1;
      }
      if (player_x < enemy_x) {
        enemy_x -= 1;
      }
      if (player_y > enemy_y) {
        enemy_y += 1;
      }
      if (player_y < enemy_y) {
        enemy_y -= 1;
      }

      if ((player_x == enemy_x || player_x == enemy_x + 1) &&
          (player_y == enemy_y || player_y == enemy_y + 1)) {
        player_state = dead;
      }
      break;
    case running:
      if (player_x > enemy_x) {
        enemy_x -= 1;
      }
      if (player_x < enemy_x) {
        enemy_x += 1;
      }
      if (player_y > enemy_y) {
        enemy_y -= 1;
      }
      if (player_y < enemy_y) {
        enemy_y += 1;
      }
      if (enemy_x < 0) {
        enemy_x = 0;
      }
      if (enemy_x > width) {
        enemy_x = width;
      }
      if (enemy_y < 0) {
        enemy_y = 0;
      }
      if (enemy_y > height) {
        enemy_y = height;
      }
      break;
    }

    switch (potion_state) {
    case available:
      if ((player_x == potion_x || player_x == potion_x + 1) &&
          (player_y == potion_y || player_y == potion_y + 1)) {
        player_state = invincible;
        enemy_state = running;
        potion_state = using;
        potion_counter = 10;
      }
      break;
    case using:
      potion_counter -= 1;
      if (potion_counter == 1) {
        player_state = vulnerable;
        enemy_state = chasing;
        potion_state = available;
        random = (random * 97) % 65533;
        potion_x = random % width;
        random = (random * 97) % 65533;
        potion_y = random % height;
      }
      break;
    }      
  }
}

figlet

Use figlet to generate ascii art text banners.

See ls /usr/share/figlet for the names of fonts to pass to -f, e.g.,

figlet -f emboss hello  

ASCII art to C array

wget https://www.cs.ucf.edu/~gazzillo/teaching/cop3223hspring26/livedemos/full_game/background.py
python3 background.py < background.array > background.c

Side scrolling

Example: background scrolling

scroll_animate.c

#include <stdio.h>
#include <stdbool.h>
#include <unistd.h>

int main(int argc, char **argv) {
  printf("\033[?25l"); // hide cursor

#include "background.c"

  int scroll_x_offset = 0;
  int scroll_y_offset = 0;

  while (1) {
    printf("\x1b[2J");
    printf("\x1b[H");

    // draw background
    background_rows = 21;
    background_cols = 41;
    background[21][41];
    for (int j = 1; j < background_rows; j++) {
      printf("\033[%d;%dH", j, 1);
      for (int i = 1; i < background_cols; i++) {
        // converting the character array position to an infinitely-sized screen
        int screen_y = (scroll_y_offset + j);
        int screen_x = (scroll_x_offset + i);
        // convert the screen position into a viewbox
        int viewbox_y = screen_y % background_rows;
        int viewbox_x = screen_x % background_cols;
        putchar(background[viewbox_y][viewbox_x]);
      }
    }
    scroll_x_offset = (scroll_x_offset + 1) % background_cols;
    fflush(stdout);

    usleep(80 * 1000);
  }
}
#include <stdio.h>
#include <stdbool.h>
#include <unistd.h>

int main(int argc, char **argv) {
  printf("\033[?25l"); // hide cursor

#include "background.c"

  int scroll_x_offset = 0;
  int scroll_y_offset = 0;

  while (1) {
    printf("\x1b[2J");
    printf("\x1b[H");

    // draw background
    background_rows = 21;
    background_cols = 41;
    background[21][41];
    for (int j = 1; j < background_rows; j++) {
      printf("\033[%d;%dH", j, 1);
      for (int i = 1; i < background_cols; i++) {
        putchar(background[(scroll_y_offset + j) % background_rows][(scroll_x_offset + i) % background_cols]);
      }
    }
    scroll_x_offset = (scroll_x_offset + 1) % background_cols;
    fflush(stdout);

    usleep(80 * 1000);
  }
}

background.c

int background_rows = 21;
int background_cols = 41;
char background[21][41] = {
  { ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ',},
  { '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_',},
  { ' ','\\', '-', '-', '-', '-', '-', '-', '-', '-', '-', '-', '-', '-', '_', '_', '-', '-', '-', '-', '/', ' ', ' ', ' ','\\', '-', '-', '-', '-', '-', '-', '-', '-', '-', '-', '-', ' ', '_', '-', '-', '/',},
  { ' ', ' ','\\', '-', '-', '-', '-', '-', '-', '-', '-', '-', '-', '/', ' ', ' ','\\', '-', '-', '/', ' ', ' ', ' ', ' ', ' ','\\', '_', '_', '/','\\', '-', '-', '_', '_', '/','\\', '/', ' ','\\', '/', ' ',},
  { ' ', ' ', ' ','\\', '_', '_', '/','\\', '-', '-', '_', '_', '/', ' ', ' ', ' ', ' ','\\', '/', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ','\\', '/', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ',},
  { ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ','\\', '/', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ',},
  { ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', '_', '/','\\', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ',},
  { ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', '/', '-', '-', '-','\\', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ',},
  { ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', '|', '-', '-', '-', '/', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ',},
  { ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ','\\', '_', '_', '/', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ',},
  { ' ', ' ', ' ', ' ', ' ', '_', '_', '_', '_', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ',},
  { ' ', ' ', ' ', ' ', '/', '-', '-', '-', '-','\\', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ',},
  { ' ', ' ', ' ', ' ','\\', '-', '-', '-', '-', '/', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ',},
  { ' ', ' ', ' ', ' ', ' ', '|', '_', '_', '/', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ',},
  { ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', '_', '/','\\', ' ', ' ', ' ', ' ', ' ', '/','\\', '_', ' ', ' ', ' ', ' ',},
  { '_', '_', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', '/', '-', '-', '-','\\', '_', '_', '_', '/', '-', '-', '-','\\', ' ', ' ', '_',},
  { '-', '-','\\', ' ', ' ', ' ', ' ', '_', '_', ' ', ' ', '/','\\', ' ', ' ', ' ', ' ', ' ', ' ', '/','\\', ' ', ' ', '_', '/', '-', '-', '-', '-', '-', '-', '-', '-', '-', '-', '-', '-', '-','\\', '/', '-',},
  { '-', '-', '-','\\', ' ', ' ', '/', '-', '-','\\', '/', '-', '-','\\', ' ', ' ', ' ', ' ', '/', '-', '-','\\', '/', '-', '-', '-', '-', '-', '-', '-', '-', '-', '-', '-', '-', '-', '-', '-', '-', '-', '-',},
  { '-', '-', '-', '-','\\', '/', '-', '-', '-', '-', '-', '-', '-', '-','\\', ' ', ' ', '/', '-', '-', '-', '-', '-', '-', '-', '-', '-', '-', '-', '-', '-', '-', '-', '-', '-', '-', '-', '-', '-', '-', '-',},
  { '-', '-', '-', '-', '-', '-', '-', '-', '-', '-', '-', '-', '-', '-', '-','\\', '/', '-', '-', '-', '-', '-', '-', '-', '-', '-', '-', '-', '-', '-', '-', '-', '-', '-', '-', '-', '-', '-', '-', '-', '-',},
  { '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_',},
};

Asynchronous input

Function Description
kbhit() Wait for keyboard input
getch() Read the input

Waiting for input

Loop while there is no keyboard input

do {
  // draw screen, game logic, etc.
 } while (! kbhit());

Read the input

Read the character

c = getch();

Simple illustration

char c = 'a';
while (1) {
  do {
    // print a character until there is keyboard input
    putchar(c);
    fflush(stdout);

    usleep(80 * 1000);
  } while (! kbhit()); // until keyboard input

  // stop looping and read the character
  c = getch();
 } // continue looping and printing the character

Full example

// https://sourceware.org/glibc/manual/latest/html_mono/libc.html#Noncanon-Example

#include <stdio.h>
#include <stdbool.h>

#include "term.c"

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

  char c = 'a';

  while (1) {
    do {
      putchar(c);
      fflush(stdout);

      usleep(80 * 1000);
    } while (! kbhit());

    c = getch();
  }
}

Example: side scroller

term.c

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdbool.h>
#include <termios.h>

// https://sourceware.org/glibc/manual/latest/html_mono/libc.html#Noncanon-Example

/* Use this variable to remember original terminal attributes. */

struct termios saved_attributes;

void
reset_input_mode (void)
{
  tcsetattr (STDIN_FILENO, TCSANOW, &saved_attributes);
}

void
set_input_mode (void)
{
  struct termios tattr;

  /* Make sure stdin is a terminal. */
  if (!isatty (STDIN_FILENO))
    {
      fprintf (stderr, "Not a terminal.\n");
      exit (EXIT_FAILURE);
    }

  /* Save the terminal attributes so we can restore them later. */
  tcgetattr (STDIN_FILENO, &saved_attributes);
  atexit (reset_input_mode);

  /* Set the funny terminal modes. */
  tcgetattr (STDIN_FILENO, &tattr);
  tattr.c_lflag &= ~(ICANON|ECHO); /* Clear ICANON and ECHO. */
  tattr.c_cc[VMIN] = 1;
  tattr.c_cc[VTIME] = 0;
  tcsetattr (STDIN_FILENO, TCSAFLUSH, &tattr);
}

// https://stackoverflow.com/questions/448944/c-non-blocking-keyboard-input

bool kbhit() {
  struct timeval tv = { 0L, 0L };
  fd_set fds;
  FD_ZERO(&fds);
  FD_SET(0, &fds);
  if (select(1, &fds, NULL, NULL, &tv) == 1) {
    return true;
  } else {
    return false;
  }
}

int getch() {
  int r;
  unsigned char c;
  do {
    if ((r = read(0, &c, sizeof(c))) < 0) {
      return '\0';  // null character on error
    }
  } while (r < 1);  // ensures read returns one character
  return c;
}

Sprite collision detection

chase.c

Background collision detection

Gravity

gravity_bounce.c

#include <stdio.h>
#include <unistd.h>

int main(int argc, char **argv) {
  int height = 20;
  int width = 40;
  float y = 5;
  float vy = 0;
  float a = .05;
  float x = 0;
  float vx = .6;
  float loss = .98;
  while (1) {
    printf("\033[2J");
    printf("\033[H");
    /* printf("\033[%d;%dH", (int)y, (int)x); */
    for (int j = 0; j < height; j++) {
      for (int i = 0; i < width; i++) {
        if ((i == (int) x) && (j == (int) y)) {
          putchar('o');
        } else {
          putchar(' ');
        }
      }
      putchar('|');
      putchar('\n');
    }
    for (int i = 0; i < width; i++) {
      putchar('^');
    }
    putchar('\n');
    /* printf("\033[%d;%dH", (int)y, (int)x); */
    /* putchar('o'); */
    if ((0 <= (x + vx)) && ((x + vx) < width)) {
      vx = vx;
    } else {
      vx = vx * -1;
    }
    x = x + vx;
    vy = vy + a;
    y = y + vy;
    if ((int) y > height) {
      vy = -1 * vy * loss;
      y = y + vy;
    }
    printf("\033[%d;%dH", height + 2, 0);
    printf("y: %f\n", y);
    printf("x: %f\n", x);
    printf("vy: %f\n", vy);
    printf("vx: %f\n", vx);
    usleep(25 * 1000);
  }
  return 0;
}

Random numbers

random.c

#include <stdio.h>
#include <stdlib.h>

int main(int argc, char **argv) {
  /* srand(2); */

  int seed = arc4random();

  printf("seed = %d\n", seed);

  srand(seed);

  int r = rand();

  printf("r = %d\n", r);

  int low = 10; // inclusive
  int high = 20; // exclusive

  int r_between = low + (r % (high - low));

  printf("r_between [%d, %d) = %d\n", low, high, r_between);
}

Author: Paul Gazzillo

Created: 2026-03-27 Fri 09:23

Validate