• Skip to main content
  • Skip to header right navigation
  • Skip to site footer
Retro Game Coders

Retro Game Coders

Retro computer/console game + dev community

  • Home
  • About
  • Blog
  • Retro Resources
    • Retro Gaming Timeline
    • Browser C64 Emulator
    • Best Retro YouTube Channels
    • New Retro Books
    • Raspberry Pi Amiga Emulation
    • MiSTer FPGA Tutorial
    • BMC64 C64 Pi
  • Contact
Home » Programming

Keyboard Control and Character Movement: Coding a Retro Game with C Part 3

In this tutorial we will write a cross-platform, retro C program that accepts keyboard control of a text-based player character on screen.

  • History of Retro Games & Retro Computers Timeline
  • Introduction to the C programming language
  • Getting Started with retro computer C Cross-Compilers

Previously we discussed how some platforms have a conio.h and some, such as Unix/Linux and MacOS don’t. This is because the header was developed for DOS/Windows, and others adopted the concept.

If we work out a solution for those platforms missing conio.h can we develop truly cross-platform C programs?

Should we even try?

Our goal

All we want to do for now is create a C program where we can control some sort of character on screen using the keys W, A, S and D for up, left, down and right movements. We will want to delete the character, move it, then redraw it.

And we want this to be compilable for as many retro systems as possible.

Easy!

Animated GIF of our final code running on Amstrad CPC and C64
Animated GIF of our final code running on Amstrad CPC and C64

Introducing conio.h

conio.h (Console Input Output) is a popular C header file that provides text input and output functions, but it is not part of the C standard. Despite it not being standard, cross-platform, or even the best solution, professors and teachers even now refuse to swap it out from their lectures, so people keep getting taught and coursework marked based on it.

While most people know it as a Borland/Turbo C (or even a Turbo Pascal) thing, apparently it first appeared in Lattice C, and it became a mainstay of DOS programming.

Most DOS or Windows C compilers therefore have the header, MacOS, UNIX and Linux do not.

Confusingly. due to the popularity on DOS/Windows, compilers such as cc65 do have a conio-compatible library.

The actual functions provided in conio.h vary from implementation to implementation, meaning just because it has a conio, doesn’t mean your identical code will still compile.

conio.h functions

conio.h should include functions that match the following. As you can see, it is a very useful set of features for anyone building a C program around textual input and output.

kbhitCheck if a key was pressed
cgetsRead a string
cscanfRead formatted string
putchWrite a character
cputsWrite a string
cprintfFormatted print string
clrscrClear screen
gotoxyPut cursor at X,Y coordinate
getchGet char input

A nice set of functions (and there are often more). If your system has the header file, all good right? Not entirely. Here is a selection from the CC65 conio implementation; Mostly the same, but slightly different.

cgetc clrscr cprintf cputc cputcxy cputs cputsxy cursor gotoxy kbhit textcolor

z88dk provides conio/DOS compatibility in the classic library:

cgets clrscr cprintf cputs getch cscanf putch gotoxy kbhit textcolor 

Simple Keyboard Controlled Character Movement with conio.h

cc65 Commodore C64 code
Commodore C64
cc65 Atari 800 code
Atari 800
z88dk Amstrad CPC code
Amstrad CPC

So can we at least create a cross-platform C program for those systems with conio?

Almost. Check this out

Simple Keyboard Controlled Character Movement with Conio

#include <stdio.h>
#include <conio.h>
// Global key variable
int key;
char x,y=10;
int main()
{
    /* Clear Screen */
    clrscr();
    /* Hide cursor */
    cursor(0);
    /* Loop until Q is pressed */
    while ((key  = cgetc()) != 'Q')
    {
        // Delete the character
        cputsxy(x,y,".");
        // keys;
        switch (key) 
        { 
            case 'w': 
                y--; 
                break; 
            case 'a': 
                x--; 
                break; 
            case 's': 
                y++; 
                break; 
            case 'd': 
                x++; 
                break; 
            default: 
                break; 
        }
        cputsxy(x,y,"@");
    
    }
    cursor(1);
    return(0);
}

Compile for Atari 800:

 cl65 -o moveXL.xex move800.c -tatari   

Compile for ZX Spectrum:

zcc +zx -vn -o move move.c -lndos -create-app -pragma-need=ansiterminal 

It almost works, but the z80 implementation does not have the cursor function, or the cputsxy

Instead we can use the following:

#include <stdio.h>
#include <conio.h>
#include <ctype.h>
// Global key variable
int key;
char x,y=10;
int main()
{
	/* Clear Screen */
	clrscr();
    /* Loop until Q is pressed */
    while ((key  = toupper(cgetc())) != 'Q')
    {
        // Delete the character
        gotoxy(x,y);
        putch('.');
        // keys;
        switch (key) 
        { 
            case 'W': 
            case 'w': 
                y--; 
                break; 
            case 'A':
            case 'a':  
                x--; 
                break; 
            case 'S': 
            case 's': 
                y++; 
                break; 
            case 'D':
            case 'd': 
                x++; 
                break; 
            default: 
                break; 
        }
        gotoxy(x,y);
        putch('@');
    
    }
    //cursor(1);
    return(0);
}

You will also notice some systems start up in uppercase or lowercase keyboard mode so some changes can be made to which keys were are detecting.

conio.h Alternative 1: ANSI Escape Codes

Going back to my very early days of the world of work, straight out of school aged 15, I spent my days sat at green on black terminal screens in the DEC VT range. Part of me still would like to get a working VT220 or similar today, though I know the CRT tends to pop after a while.

These guys were super expensive “dumb terminals” – they had only enough technology inside to connect to a remote computer (slow serial connections usually) and handle input and output.

To make this system work across various vender implementations, and then color Bulletin Boards and more, required a standard for putting stuff on these screens, the ANSI standard.

Not all systems adopted this standard, unfortunately, so you get situations where the Commodore family for example use PETSCII and their own, different escape codes.

[2JClears the window/terminal
x;yHMove the cursor to x, y coordinate
?25lHide the cursor
?25hShow the cursor.
[cmSet text colour (30 to 37) and background (40 to 47):
0. Black [30m
1. Red [31m
2. Green [32m
3. Yellow [33m
4. Blue [34m
5. Magenta [35m
6. Cyan [36m
7. White [37m

Using these codes is as simple as printing them out to the terminal with an escape character before it to show that the code following should be interpreted as a control sequence rather than output. The terminal software then interprets the code to the best of its ability (eg. a monochrome CRT won’t be able to display color).

ANSI Escape Codes in C
Using ANSI Escape Codes in C

This is fine for putting things on the screen, but we will still need a way to get input from the user, and preferably a general-purpose one that is not limited to one hardware implementation.

One workaround (but a messy one) is to use system calls for the target operating system you are working on, for example in Linux/Unix/MacOS you can use the following function to fill in the missing getch()

getch() for MacOS/Linux/Unix

On Unix-like systems we can turn echo on/off using the echo/-echo parameter to stty

We can also set that we want raw input rather than have the terminal interpret the keypresses. Unfortunately this does mean it won’t respond to ctrl-c to quit out.

/* Get key input     */
/* requires stdlib.h */
char getch()
{
    char c;
    system("stty raw -echo");
    c=getchar();
    system("stty -raw echo");
    return c;
}
Moving our character around the terminal using our version of getch()

conio.h Alternative 2: Curses and Ncurses

Ncurses, or New Curses, is a free, cross-platform library for creating text mode graphical interfaces. It is still being maintained and forms the basis for many tools people rely on daily.

Curses, named for Cursor Optimization, was developed by Ken Arnold at Berkley for BSD. Ncurses (pronounced “enn-curses”) expanded on the subsequent work by Pavel Curtis on pcurses, and has since spread it to many systems and languages, including Python, Ruby, PHP, and JavaScript.

Simple Keyboard Controlled Character Movement with Ncurses

#include <stdio.h>
#include <ncurses.h>
/* Clear screen and set up Curses */
void clrscr() 
{
    /* Initialise the screen */
    initscr();
	erase();
    noecho();
    raw();
  	move(0, 0);
    /* Cursor off */
    curs_set(0);
    refresh();
}
/* Move cursor to coordinate */
void gotoxy(unsigned int x, unsigned int y)
{
    move(y,x);
}
/* Put string at coordinate */
void cputsxy(unsigned int x, unsigned int y, char outString[255])
{
    mvprintw(y,x, outString);
}
// Global key variable
int key;
char x,y=10;
int main()
{
	/* Clear Screen */
	clrscr();
    /* Loop until Q is pressed */
    while ((key  = getch()) != 'Q')
    {
        /* Delete the character */
        cputsxy(x,y,".");
        /* Handle keys */
        switch (key) 
        { 
            case 'w': 
                y--; 
                break; 
            case 'a': 
                x--; 
                break; 
            case 's': 
                y++; 
                break; 
            case 'd': 
                x++; 
                break; 
            default: 
                break; 
        }
        /* Print our character */
        cputsxy(x,y,"@");
        /* Refresh the terminal */
        refresh();
    
    }
    /* Turn echo and cursor back on */
    echo();
    curs_set(1);
    /* End program */
    return(0);
}

To use ncurses library functions, you have to include ncurses.h in your program and link using the parameter -lncurses

cc -o prog prog.c -lncurses

Unfortunately, from what I have found, while modern desktops are covered, and the Amiga, there is no ncurses for 8-bit machines. Please let me know if there is one!

What should we do?

My personal approach will be to write game logic separate from graphics rendering. I would also like to split out controls (keyboard, mouse, joystick?)

As any Amiga owner back in the 1980s will tell you (or perhaps even more convincing, Amstrad owners), directly porting games from one system to another tends to give you the lowest-common experience. Amstrad owners often got to play Spectrum conversions based on what the Spectrum was capable of, Amiga owners to begin just got Atari ST ports with very little in the way of upgrade. Even on the Amiga and ST, when more powerful graphics were possible on the 1200 and STE, game companies targeted the base systems.

Imagine if the C64 was only ever given Commodore PET or Vic 20 games?

Back in the day we used to type BASIC programs into our home computers from magazine listings. Invariably there would be a section telling you how to change the listing so it would work on your computer versus the code originally printed.

If we split our code and have the common parts common but customize for the target machine where possible, we can have the best native experience on each system. We won’t have to restrict the Amiga graphics to the Apple ][ color palette 😉

As mentioned previously, each system also needs individual handling in terms of emulator and the file formats those emulators accept, and then optionally how to get those files onto the physical hardware.

All of this is a long way to warn you of forthcoming segues, rabbit holes, and diversions, as we build games for every one of my collected retro systems, and create retro-style games for modern systems. Where code can be the same we will do that, where it needs to be different we will examine what the differences are.

If you like it, share it! (Please - because it really helps)

  • Tweet
Category: ProgrammingTag: amstrad, atari, c programming, c64, imported, retro, retro computers, retro games, spectrum
Previous Post: « Getting Started Programming in C: Coding a Retro Game with C Part 2
Next Post: Ultimate 64: Commodore 64 FPGA Motherboard Replacement/Upgrade Review ultimate64 c64 motherboard replacement »

Retro Game Coders

Retro computer/console game + dev community by Chris Garrett

  • Facebook
  • Twitter
  • Instagram
  • YouTube

Maker Hacks ・ Geeky Game Master

© Copyright 2021 Chris Garrett

Privacy ﹒ Terms of Service

Return to top