views:

787

answers:

4

I'm working on making an ASCII based game, and everywhere I look people are saying to use Console.Write() from MSDN, which is dandy and all if you're using Windows, but I'm not.

And thus, I'm trying to write a function, or group of functions in C that can alternate between two screen buffers, and write them to the screen, similar to what man pages would be like, as well as pico, vim, and emacs.

I have the buffer's working, and found an old ASCII game for linux called 0verkill that uses C and putchar() to place each character on the screen, but all of my attempts to re-create that, result in a continuous flow of text, and not a window sized panel of static text. I really don't want to use any external libraries like curses (because that would reduce portability) and would like to keep to ansi standards if at all possible.

Thanks!

+3  A: 

I really don't want to use any external libraries like curses (because that would reduce portability)

What? Libraries like curses and ncurses are designed to make this kind of thing more portable, because...

and would like to keep to ansi standards if at all possible.

...there is no ANSI standard (for C at least) for what you want. Each operating system implements this kind of behavior differently, so if you want a portable way to do it, you need to use a library. Honestly, I'd hate to have to develop for a system that didn't have ncurses ported to it. Imagine all the programs you wouldn't be able to use without it.

Chris Lutz
I agree with what you said about ncurses being amazing, but welcome to osx, (there is an ncurses port), but how do programs like emacs (installed by default) run without it?
Ryan Rohrer
OS X (at least, my OS X) has ncurses installed by default.
Chris Lutz
Well it seems as I might as well delve into the documentation of ncurses.... thanks for your help!
Ryan Rohrer
+5  A: 

I think what you are looking for is the ANSI control character ESC[2J which clears the screen. You would call that after any change of state to "refresh" the console screen.

See this page to learn about the rest of them. Using these codes you can define colors and formatting (spacing, alignment, indenting etc) on the console.

Dale Halliwell
Thanks, although when i try that, it keeps scrolling the terminal window longer and longer.... and doesn't place the old putchars on top of the new ones, ever esc[2j makes a blank space the size of my console window.
Ryan Rohrer
Yes, I think that's all you can do, it scrolls the window the number of lines as your console, then you redraw everything, then scroll again. This is the only way I'm aware of that these things work to give the appearance of a static window, but I don't think you can have actual static text like in a windows control (but even that's not really static, it is just constantly being repainted): you are dealing with a console - just a stream - after all.
Dale Halliwell
+2  A: 

There is an ANSI standard X3.64, also ISO/IEC 6429 which describes the DEC VT100 terminal. The standard describes certain escape sequences for color and cursor positioning that a compliant terminal-emulator will recognize, which will be basically all X terminals, but on Windows not necessarily (possibly you need the user to load ansi.sys). It is this last ugly inconsistency that illustrates why you should be using ncurses which abstracts away such detail.

Cirno de Bergerac
+1 for the last sentence. Why would you want to write a buggy, incomplete, and hard to port version of an ages-old, well-tested, and widely-ported library?
Chris Lutz
+3  A: 

An example header and source file illustrating a way to abstract curses from the application. Gathering dust; wrote it over 15 years ago. Caveat emptor.

cursemu.h

/***************************************************************************
 *                                                                          
 *  DO NOT CHANGE ANYTHING BETWEEN THIS LINE AND THE NEXT LINE THAT HAS THE 
 *  WORDS "KLAATU BARRATA NIKTO" ON IT                                      
 *                                                                          
 ***************************************************************************/
#ifndef X__CURSEMU__H                                                        
#define X__CURSEMU__H                                                        

#include <stdio.h>

#ifdef  linux
#define _POSIX_VERSION
#endif                

#ifndef _POSIX_VERSION
#include <sgtty.h>    
#define  USE_OLD_TTY  
#include <sys/ioctl.h>
#undef  USE_OLD_TTY   

#ifndef  CBREAK
#define CBREAK RAW
#endif            

#if !defined(sun) && !defined(sequent) && !defined(hpux) && \
    !defined(_AIX) && !defined(aix)                          
#include <strings.h>                                         
#define strchr index                                         
#else                                                        
#include <string.h>                                          
#endif                                                       
#else                                                        
#include <string.h>                                          
#include <termios.h>                                         
#endif                                                       

#include <errno.h>
#include <sys/types.h>
#include <pwd.h>      
#include <sys/time.h> 
#include <sys/file.h> 
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>     
#include <signal.h>    

/* Keep looking ... */

int _tty_ch;

#ifdef  _POSIX_VERSION
struct termios _tty;  
tcflag_t _res_iflg,   
    _res_lflg;        

#define cbreak()(_tty.c_lflag&=~ICANON, \
  tcsetattr( _tty_ch, TCSANOW, &_tty ))  

#define noecho()(_tty.c_lflag &= ~(ECHO|ICRNL), \
  tcsetattr( _tty_ch, TCSADRAIN, &_tty ))        

#define savetty()((void) tcgetattr(_tty_ch, &_tty), \
  _res_iflg = _tty.c_iflag, _res_lflg = _tty.c_lflag )

#define resetty()(_tty.c_iflag = _res_iflg, _tty.c_lflag = _res_lflg,\
  (void) tcsetattr(_tty_ch, TCSADRAIN, &_tty))                        

#define erasechar()(_tty.c_cc[VERASE])
#else                                 
struct sgttyb _tty;                   
int _res_flg;                         

#define cbreak()(_tty.sg_flags|=CBREAK, ioctl(_tty_ch, TIOCSETP, &_tty))

#define noecho()(_tty.sg_flags &= ~(ECHO|CRMOD), \
  ioctl( _tty_ch, TIOCSETP, &_tty ))              

#define savetty()((void) ioctl(_tty_ch, TIOCGETP, &_tty), \
  _res_flg = _tty.sg_flags )                               

#define resetty()(_tty.sg_flags = _res_flg, \
  (void) ioctl(_tty_ch, TIOCSETP, &_tty))    
#define erasechar()(_tty.sg_erase)           
#endif                                       

/* KLAATU BARRATA NIKTO */

#define  TERMCAP_LENGTH 1024

struct CtrlSeq
{             
  char termcap[ TERMCAP_LENGTH ];

  int numRows, numCols;

  /*  These pointers are indexes into the termcap buffer, and represent the
   *  control sequences neccessary to send to the terminal window to perform
   *  their appropriately named feature.
   */
  char *highlight,
       *endMode,            /* End highlight mode, and other modes. */
       *clearScr,
       *clearEol,
       *scrollRegion,
       *moveCursor,
       *deleteRow,
       *insertRow,
       *saveCursor,         /* Save the current cursor position */
       *restoreCursor;      /* Restore the saved cursor position */

  int dumbTerm,             /* 1 if the terminal is a dumb terminal */
      flush;                /* 1 if the emulation should flush stdout */
};

struct CtrlSeq ctrlSeq;

#define DEFAULT_COLS    80
#define DEFAULT_ROWS    24

void ce_flush( int toSet );
void ce_puts( char *str );
void ce_gotoRowCol( int row, int col );

void ce_writeStrRowCol( char *theText, int row, int col );
void ce_writeStr( char *theText );
void ce_writeCharRowCol( char theChar, int row, int col );
void ce_writeChar( char theChar );

void ce_clearScreen( void );
void ce_clearEol( void );

void ce_highlight( int on );
void ce_scrollRegion( int row1, int row2 );
void ce_deleteRow( int row );
void ce_insertRow( int row );
void ce_saveCursor( void );
void ce_restoreCursor( void );

int ce_getRows( void );
int ce_getCols( void );

#endif

cursemu.c

#include "cursemu.h"

int putchar_x( int c )
{                     
  return( putchar( c ) );
}                        

/*  Returns 0 on success, -1 on error
 */                                  
int ce_startCurses( void )           
{                                    
  char *ptr,                         
       tempBuff[ 1024 ];             
  int  result = 0;                   

  if( (ptr = (char *)getenv( "TERM" )) != NULL )
    result = tgetent( tempBuff, ptr );          
  else                                          
    result = tgetent( tempBuff, "vt100" );      

  if( result < 1 )
  {               
    perror( "FATAL Error: No termcap entry found (even tried vt100)!\n" );
    return( -1 );                                                         
  }                                                                       

  ptr = ctrlSeq.termcap;

  if( (ctrlSeq.numCols = tgetnum( "co" )) == -1 )
    ctrlSeq.numCols = DEFAULT_COLS;              
  if( (ctrlSeq.numRows = tgetnum( "li" )) == -1 )
    ctrlSeq.numRows = DEFAULT_ROWS;              

  if( (ctrlSeq.moveCursor = (char *)tgetstr( "cm", &ptr )) == NULL )
    ctrlSeq.moveCursor = (char *)tgetstr( "cl", &ptr );             
  if( (ctrlSeq.highlight = (char *)tgetstr( "mr", &ptr )) == NULL ) 
    ctrlSeq.highlight = (char *)tgetstr( "md", &ptr );              

  ctrlSeq.endMode       = (char *)tgetstr( "me", &ptr );
  ctrlSeq.clearEol      = (char *)tgetstr( "ce", &ptr );
  ctrlSeq.clearScr      = (char *)tgetstr( "cl", &ptr );
  ctrlSeq.scrollRegion  = (char *)tgetstr( "cs", &ptr );
  ctrlSeq.deleteRow     = (char *)tgetstr( "dl", &ptr );
  ctrlSeq.insertRow     = (char *)tgetstr( "al", &ptr );
  ctrlSeq.saveCursor    = (char *)tgetstr( "sc", &ptr );
  ctrlSeq.restoreCursor = (char *)tgetstr( "rc", &ptr );

  ctrlSeq.dumbTerm = (ctrlSeq.moveCursor == NULL) ||
                     (ctrlSeq.scrollRegion == NULL) ||
                     (ctrlSeq.saveCursor == NULL) ||  
                     (ctrlSeq.restoreCursor == NULL) ||
                     (ctrlSeq.clearEol == NULL);       

  ctrlSeq.flush = 1;

  if( !ctrlSeq.dumbTerm )
  {                      
    if( (_tty_ch = open( "/dev/tty", O_RDWR, 0 ) ) == -1 )
      _tty_ch = 0;                                        

    savetty();
    cbreak(); 
    noecho(); 
    return( 0 );
  }             

  return( -1 );
}              

int ce_endCurses( void )
{                       
  ce_scrollRegion( -1, -1 );
  ce_gotoRowCol( ce_getRows() - 1, 0 );
  resetty();                           
}                                      

void ce_flush( int toSet )
{                         
  ctrlSeq.flush = toSet;  

  if( toSet == 1 )
    fflush( stdout );
}                    

void ce_puts( char *str )
{                        
  tputs( str, 0, putchar_x );

  if( ctrlSeq.flush )
    fflush( stdout );
}                    

void ce_gotoRowCol( int row, int col )
{                                     
  if( row > ctrlSeq.numRows )         
    row = ctrlSeq.numRows;            
  if( col > ctrlSeq.numCols )         
    col = ctrlSeq.numCols;            

  ce_puts( (char *)tgoto( ctrlSeq.moveCursor, col, row ) );
}                                                          

void ce_writeStrRowCol( char *theText, int row, int col )
{                                                        
  ce_flush( 0 );                                         
  ce_gotoRowCol( row, col );                             
  ce_writeStr( theText );                                
  ce_flush( 1 );                                         
}                                                        

void ce_writeStr( char *theText )
{                                
  ce_flush( 0 );                 
  printf( "%s", theText );       
  ce_flush( 1 );                 
}                                

void ce_writeCharRowCol( char theChar, int row, int col )
{                                                        
  ce_flush( 0 );                                         
  ce_gotoRowCol( row, col );                             
  ce_writeChar( theChar );                               
  ce_flush( 1 );                                         
}                                                        

void ce_writeChar( char theChar )
{                                
  ce_flush( 0 );                 
  printf( "%c", theChar );       
  ce_flush( 1 );                 
}                                

void ce_clearScreen( void )
{                          
  ce_puts( ctrlSeq.clearScr );
}

void ce_clearEol( void )
{
  ce_puts( ctrlSeq.clearEol );
}

void ce_highlight( int on )
{
  if( on == 0 )
    ce_puts( ctrlSeq.endMode );
  else
    ce_puts( ctrlSeq.highlight );
}

void ce_scrollRegion( int row1, int row2 )
{
  ce_puts( (char *)tgoto( ctrlSeq.scrollRegion, row1, row2 ) );
}

void ce_deleteRow( int row )
{
  ce_gotoRowCol( row, 0 );
  ce_puts( ctrlSeq.deleteRow );
}

void ce_insertRow( int row )
{
  ce_gotoRowCol( row, 0 );
  ce_puts( ctrlSeq.insertRow );
}

void ce_saveCursor( void )
{
  ce_puts( ctrlSeq.saveCursor );
}

void ce_restoreCursor( void )
{
  ce_puts( ctrlSeq.restoreCursor );
}

int ce_getRows( void )
{
  return( ctrlSeq.numRows );
}

int ce_getCols( void )
{
  return( ctrlSeq.numCols );
}

Compiling

Requires:

gcc -o cursemu.o -lcurses -ltermcap

Good Luck!

Dave Jarvis
oh wow, thanks!
Ryan Rohrer