Creating games with C using ASCII characters

While it is possible to use full fledged graphics to create games, but learning how to use a game engine or a game development framework usually takes time. It is equally fun and probably easier to use only ASCII characters and no graphics libraries or game engines to make simple games. ASCII characters can be arranged or joined to create various entities inside the game. This approach works reasonably well for very simple 2-D games but for production quality games, you’d want to look at game engines or game development frameworks such as Unity as there are additional things to consider such as memory and resource management, hardware acceleration for graphics, media management (assets, pictures, fonts, music, etc), and so on.

Here’s the source code for a simple “Snake” style game that I call ‘Caterpillar’. This implementation utilizes a simple looped update mechanism. What this essentially means is that there is one while loop in main() within which all operations occur, such as updating the position of entities on the screen, reading prior keystroke, updating score, etc. This was originally written for Windows XP as a personal project over a weekend in college and then improved a bit later. It does work on windows 7 and 10 but only in windowed mode (not full-screen). It is completely developed in C. The programming style here is definitely not the gold standard for C programming and could be better and I’d have likely written it better overall if it was done recently. Here are some improvements I can think of:
– The main() function is way too large and should ideally be split into additional functions
– We could probably use some custom types to hold data, use a linked list to hold snake position information that can be extended indefinitely in place of the current size-limited array.
– Also input reading and UI update could be smoother. Ideally use a separate thread for updating scores and level so it doesn’t interfere with UI updates.
On the plus side, it does work :)

Here are some screenshots:

Snake2StartupScreen

Snake

Video:

Here’s the source code:


/*Caterpillar game version 3.0*/
/*Created by Navjodh on 14.03.08*/
/*Copyright (C) Navjodh 2008-2017*/

#include <stdio.h>         /*include required header files*/
#include <conio.h>
#include <windows.h>
#include <time.h>

void gotoxy(short x, short y);      /*Places Cursor on the screen at input coordinates x and y*/
void clrscr(void);                  /*clears screen*/
void ret_color(void);               /*sets console back to normal(white) color*/
void setcolor(int color);           /*sets console to an input color number*/
void colorfn(void);                 /*sets console to a random color*/
void display_food(void);            /*displays food at a random location on the screen*/
void welcome_mesg();                /*displays a welcome message*/
void buildwalls();                  /*Displays outer walls*/
void fs();                          /*puts the screen in fullscreen mode*/
void check_hi_scores(int score);     /*compares the input high scores with those already saved in a file
                                      a new high score is saved in the file. also the file contents are 
                                      displayed*/

                                      /*Declaration of global variables*/
int fdx,fdy;/*these are global variables to store food coordinates.*/

main()
{
     fs();          /*this function simulates alt+enter for fullscreen display*/
     int rst=0;     /*flag to check whether restart or initialize*/
     restart:       /*this is a goto label    */
     clrscr();
     int x,y;
     x=24;          /*this is the position where the snake will be at the beginning of the game*/
     y=35;
     char cont;     /*to save user response on exit confirmation*/
     int snakelength=4;  /*assign a default length to the caterpillar/snake*/
     int spx[200]={24,24,24,24,24};/*array size can be extended to increase snake length*/
     int spy[200]={31,32,33,34,35}; 
     int i;   /*needed in for loop*/
     
     int foodflag=0;/*use: when food is eaten, print another food somewhere on screen
                           is zero when food is needed to be printed
                           is one when food already exists on screen*/
                           
     int xychanged=1;      /*this flag indicates change in coordinates. initially set to true*/
     int collisionflag=0;  /*this flag indicates collision.1 when true*/
     
     int level=0;            /*the level mainly as of now represents the speed of game*/
     int delaytime;
     delaytime=100;          /*higher delaytime=lower level*/
     int score=0;
     
     char keypressed='i';    /*inialize to prevent this variable from having a random garbage value*/
     
     if(rst==0)
     welcome_mesg();         /*call function that displays welcome mesg*/
     buildwalls();           /*call function that displays boundary walls*/
     
     gotoxy(0,0); 
     printf("\nPress q during gameplay to quit the game...\n");
     
     while(1)
     {
     
         if(foodflag==0)
         {
          display_food();
           /*the following code prevents food from being placed on the body of caterpillar*/
           for(i=0;i<(snakelength-1);i++)  
             {
              if((spx[i]==fdx)&&(spy[i]==fdy))
                  {
                      display_food();
                      break;
                  }
              }
           foodflag=1;
          }
         
         spx[snakelength]=x;         /*save current snake position coords in 5th posn in the arrays*/
         spy[snakelength]=y;
         
         if(xychanged==1)
           for(i=0;i<snakelength;i++)    /*shift coordinates on position left in the array*/
           {
            spx[i]=spx[i+1];
            spy[i]=spy[i+1];
            }
         xychanged=0;
         
         gotoxy(x,y);        /*print snake's head*/
         setcolor(11);
         printf("%c",178);
         ret_color();
         
         gotoxy(spx[0],spy[0]);/*this piece of code removes snake's trail*/
               setcolor(0);
               printf("%c",178);
               ret_color();
             
         /*Prototype: int kbhit(void); Header File: conio.h
                       Explanation: 
                       It returns a non-zero integer if a key is in the keyboard buffer.
                        It will not wait for a key to be pressed.*/
                        
         while(kbhit())
         {
              keypressed=getch();/*assign whatever is there in the keyboard buffer to the
                                  variable keypressed. 
                                  if nothing has been pressed these 'whiles' will be skipped*/
                            while(kbhit())
                            keypressed=getch();
         }
         
         Sleep(delaytime);  /*always use capital S!!*/
         
         /*when up key is pressed, add one to the up coordinate and  display the result and so on*/
         switch(keypressed)
         {
                    case 72:
                    xychanged=1;
                    y--;
                    break;
                    
                    case 75:
                    xychanged=1;
                    x--;
                     break;
                    
                    case 80:
                    xychanged=1;
                    y++;
                     break;
                    
                    case 77:
                    xychanged=1;
                    x++;
                    break;
                    
                    case 'q':   /*the user presses q key*/
                         {
                             gotoxy(0,0);
                             printf("Are You Sure You Want to Quit (Y/N)?                        \n");
                             printf("Press 'g' for New game...                                   \n");             
                             
                             gotoxy(0,2);
                             fflush(stdin);    /*clear up kb buffer to recieve input*/
                             scanf("%c",&cont);
                             if((cont=='y')||(cont=='Y')) 
                             {
                                clrscr();
                                check_hi_scores(score);
                                printf("\nExiting\nPlease Wait...");
                                Sleep(500);
                                exit(1);
                             }
                             else if((cont=='g'))         /*if player chooses a new game*/
                                  {
                                  check_hi_scores(score);
                                  clrscr();
                                  rst=1;
                                  goto restart; 
                                  }
                         }
                    keypressed='p';    /*assign this variable any value other than q*/
                    gotoxy(0,0);
                    printf("press q during gameplay to quit the game...\n                                         \n");                        
                    break;
                    
                    default:
                    break;
         } 
         
         
        /*here we check whether collision has taken place with the side walls*/
        if((x>=73)||(x<=2)||(y>=48)||(y<=5))
        collisionflag=1;
    
       else
        {
             for(i=0;i<(snakelength-2);i++)    /*to check whether snake has collided with itself*/
             {
              if((snakelength>5)&&(spx[i]==x)&&(spy[i]==y))/*if coordinates of snake's head match any of rest of snake coordinates*/
                  {
                      collisionflag=1;
                      break;
                  }
              }
        }
        
        if(collisionflag==1)          /*if collision has taken place display the following
                                      else continue as usual*/
         {
                             Sleep(1000);  /*small delay before displaying 'game over' message*/
                             
                             gotoxy(34,24);
                             printf("            ");
                             setcolor(10);
                             gotoxy(34,24);
                             printf("GAME OVER!!");
                             Sleep(500);
                             gotoxy(34,24);
                             printf("           ");
                             Sleep(500);
                             gotoxy(34,24);
                             printf("GAME OVER!!");
                             Sleep(500);
                             gotoxy(34,24);
                             printf("           ");
                             Sleep(500);
                             gotoxy(34,24);
                             printf("GAME OVER!!");
                             Sleep(1000);
                             clrscr();
                             check_hi_scores(score);
                             clrscr();
                             gotoxy(1,2);
                             printf("New Game?(Y/N)\n ");
                             fflush(stdin);
                             scanf("%c",&cont);
                             if(cont=='y')
                             {
                             clrscr();
                             ret_color();
                             rst=1;
                             goto restart;
                             }
                                   
                             else
                             {   
                                 clrscr();
                                 ret_color();
                                 printf("\nExiting...");
                                 Sleep(500);
                                 exit(1);
                             }
               }
         
         if((fdx==x)&&(fdy==y))  /*this will hold true when food and snake's head's coords match*/
         {
               foodflag=0;       /*reset foodflag so that food is printed in the next iteration*/
               score++;
               snakelength++;
               if((score%10)==0)
               {
                       level++;
                       gotoxy(64,1);
                       printf("LEVEL: %d",level);/*this way level will be updated on screen only when there's 
                                                   an increment*/
                       delaytime=abs(delaytime-15);/*decrease delay time for each increment of 100 in score*/
               }                                    /*this is equivalent to increasing level*/
               
               gotoxy(64,3);
               printf("SCORE: %d",score*10);
               /*display score. printf is included within if statement to make 
               the process more efficient.(no need to update score unless food is
               eaten)*/  
                                     
         }
     }
}



/*  x varies from 0 to 73 y from 0 to 49 */
void gotoxy(short x, short y) 
{

	HANDLE hConsoleOutput;
	COORD Cursor_Pos = {x, y};

	hConsoleOutput = GetStdHandle(STD_OUTPUT_HANDLE);
	SetConsoleCursorPosition(hConsoleOutput, Cursor_Pos);
}

void clrscr(void) 
{
	CONSOLE_SCREEN_BUFFER_INFO csbi;
	HANDLE hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
	COORD coord = {0, 0};
	DWORD count;
	
	GetConsoleScreenBufferInfo(hStdOut, &csbi);
	FillConsoleOutputCharacter(hStdOut, ' ', csbi.dwSize.X * csbi.dwSize.Y, coord, &count);
	SetConsoleCursorPosition(hStdOut, coord);
}

void ret_color()/*this sets the color of display back to white*/
{
  HANDLE  hConsole;
 
  hConsole = GetStdHandle(STD_OUTPUT_HANDLE);
  SetConsoleTextAttribute(hConsole,7);
}
   
void setcolor(int color)              /*sets console to an input color number*/
{
  HANDLE  hConsole;
  hConsole = GetStdHandle(STD_OUTPUT_HANDLE);
  SetConsoleTextAttribute(hConsole,color);
}
     
void colorfn()                            /*sets console to a random color*/
{
  int color;
  HANDLE  hConsole;
  hConsole = GetStdHandle(STD_OUTPUT_HANDLE);
   
  srand ( time(NULL) );
  color=rand() % 10 + 5;                     /*number between 1 to 15*/
  
  SetConsoleTextAttribute(hConsole,color);
}
    
void display_food()
{
     int x,y;
         /*generate a random number for selecting food coordinates...     */
     srand ( time(NULL) );
     x=rand() % 68+ 3;  /*2 to 73 and 5 to 48*/
     y=rand() % 40 + 6;
     
     fdx=x;   /*save current food coordinates in global variables*/
     fdy=y;     /*as these will be needed by other functions also*/
     
     gotoxy(x,y);    /*here we print the food*/
     colorfn();       /*get random colour*/
     printf("%c",177);
     ret_color();     /*set color back to white*/
}

void welcome_mesg()
{    
     int u;
     setcolor(10);
     for(u=1;u<=77;u++)       /*display moving bars in welcome note*/
     {
           gotoxy(u,5);
           printf("%c",176);
           gotoxy(78-u,15);
           printf("%c",176);
           Sleep(25);
     }
     
     gotoxy(30,9);          /*welcome message*/
     setcolor(14);
     printf("Curly the Caterpillar V2.0\n");
     setcolor(11);
     gotoxy(32,11);
     printf("By Navjodh");
     ret_color();
     Sleep(1000);
     gotoxy(16,20);
     printf("Rules: Lead the Caterpillar to its food and gain Points...");
     gotoxy(16,21);
     printf("       Avoid hitting the Walls and don't eat yourself up!!");
     gotoxy(16,22);
     printf("       Level increases with increase in the points you gain");
     gotoxy(16,25);
     printf("Controls: Use Arrow Keys To Navigate"); 
   
     gotoxy(16,31);
     printf("Press any key to Start Game...");
     getch();
     clrscr();
     
}

void buildwalls()
{
     int wx,wy;
     
     setcolor(14);
     for(wx=2;wx<=73;wx++)
     {
         gotoxy(wx,5);
         printf("%c",205);
     }
     
     for(wx=2;wx<=73;wx++)
     {
         gotoxy(wx,48);
         printf("%c",205);
     }
     
     for(wy=5;wy<=48;wy++)
     {
         gotoxy(2,wy);
         printf("%c",186);
     }
     
     for(wy=5;wy<=48;wy++)
     {
         gotoxy(73,wy);
         printf("%c",186);
     }
     
     gotoxy(2,5);         /*special ascii characters at the four corners*/
     printf("%c",201);
     
     gotoxy(2,48);
     printf("%c",200);
     
     gotoxy(73,5);
     printf("%c",187);
     
     gotoxy(73,48);
     printf("%c",188);
     ret_color();         /*change color back to white*/
}

void fs()                 /*this function makes the program start in fullscreen(it simulates ALT+ENTER)*/
{
    keybd_event(VK_MENU, 0x38, 0, 0);
    keybd_event(VK_RETURN, 0x1c, 0, 0);
    keybd_event(VK_RETURN, 0X1c, KEYEVENTF_KEYUP, 0);
    keybd_event(VK_MENU, 0x38, KEYEVENTF_KEYUP, 0);
}

void check_hi_scores(int score)
{
      clrscr();
      static char tempname[30];/*for temporary storage of player name to prevent array overflow in case 
                               the user types in more than 5 characters(which they most certainly will :) )*/
      int namelen=0;
      
      score=score*10;
      FILE *fp;
      
      int i=0,j,k,m,temp;
      char stemp[5];     /*variable to store names during sorting */
      int sort=0;
      
      struct player            /*defining player as a structure enables convenient handling*/
      {                        /*of player information i.e. name and score that are */
             char name[5];    /*different data types*/
             int score;
      };
      struct player p[5];
      
      /*read high score information from file and save in struct variables*/
      fp=fopen("hiscores.txt","r");
            
      if(fp==NULL)
      {printf("\nError:Unable to open file!\n");
      getch();
      exit(1);}
      
     
      while((fscanf(fp,"%s    %d",p[i].name,&p[i].score))!= EOF)
      {
               i++;
      }
      
      fclose(fp);
      
      /*compare the current score with all stored high scores*/
      for(m=0;m<5;m++)
      {
               if(score>p[m].score)    /*if the current score is higher than any stored scores*/
               {                         /*then proceed to save a new player*/
               setcolor(10);             /*change color to green*/
               printf("\n\n\t\t\tHIGH SCORE!!\n");
               setcolor(7);              /*revert to normal color*/
               printf("\n\nEnter your name(Maximum of 5 characters)\n");
               fflush(stdin);
               scanf("%s",tempname);
               namelen=strlen(tempname);
               
              if(namelen>=5)/*this is to ensure entry of exactly 5 characters.
                             if greater truncate to 5. if smaller pad with blank spaces or underscores*/
               {                        
                 for(i=0;i<5;i++)
                 p[4].name[i]=tempname[i];/*end of for loop*/
                 p[4].name[i]='\0';
               }
               
               else
               {
                   for(i=0;i<namelen;i++)/*fill in the name characters*/
                   p[4].name[i]=tempname[i];
                   
                   for(i=namelen;i<5;i++)/*pad the rest with spaces*/
                   p[4].name[i]='_';
                   p[4].name[i]='\0';
                   
               }
     
     
               p[4].score=score;
                     
               sort=1;/*set flag for sorting further in the prog*/
               break;
               }
      }       
      
      /*compare the current score with all high score
      if it is greater than any high score then proceed to save it
      save the present high score in place of the present lowest score saved
      lowest score will be at position 5(posn 4 starting from 0)
      
      
      perform sorting
      
      skip this step if there are no new high scores to be added
      (old high scores should be printed in case there's no new high score to be added)*/

if(sort==1)
{
      for(j=0;j<i;j++)
	{
		for(k=0;k<i-1;k++)
		{
			if(p[k].score<p[k+1].score)
			{
				temp=p[k].score;
                strcpy(stemp,p[k].name);	/*interchange immediate terms for ascending order*/
				p[k].score=p[k+1].score;    /*interchange names along with the scores*/
				strcpy(p[k].name,p[k+1].name);
				p[k+1].score=temp;
				strcpy(p[k+1].name,stemp);
			}
		}
	}
}

printf("\n\t\t\tHIGH  SCORES");
setcolor(14);         /*yellowish color*/
printf("\n\n\n\t\t\tName      Score\n");
setcolor(8);                  /*dark grey*/
printf("\t\t\t***************\n");
setcolor(11);                     /*greenish*/
	for(m=0;m<i;m++)
		printf("\t\t\t%s    %d\n",p[m].name,p[m].score);
setcolor(8);                /*dark grey*/
printf("\t\t\t***************\n");
setcolor(7);                      /*revert to normal white color*/

                                 /*write the sorted list to file*/

	fp=fopen("hiscores.txt","w");/*open file in write mode*/
	
	if(fp==NULL)
	{printf("Error\nUnable to open file");
	Sleep(500);
	exit(1);}

    for(i=0;i<5;i++)
	fprintf(fp,"%s    %d\n",p[i].name,p[i].score);
		
	fclose(fp);
	printf("\n\n\t\t\tHigh Scores saved!\n");
	printf("\n\t\t\tPress Any Key to Continue...");

    getch();


}                   




Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>