views:

104

answers:

2

Here's an oddity from the past!

I'm writing an ASCII Pong game for the command prompt (Yes yes oldschool) and I'm writing to the video memory directly (Add. 0xB8000000) so I know I'm rendering quickly (As opposed to gotoxy and then printf rendering)

My code works fine, the code compiles fine under Turbo C++ V1.01 BUT the animation lags... now hold on hold on, there's a cavaet! Under my super fast boosted turbo Dell Core 2 Duo this seems logical however when I hold a key on the keyboard the animation becomes smooth as a newly compiled baby's bottom.

I thought maybe it was because I was slowing the computer down by overloading the keyboard buffer (wtf really? come on...) but then I quickly smartened up and tried compiling for DJGPP and Tiny C Compiler to test if the results are the same. On Tiny C Compiler I found I coulnd't compile 'far' pointer types... still confused on that one but I was able to compile for DJGPP and it the animation ran smoothly!

I want to compile this and have it work for Turbo C++ but this problem has been plagueing me for the past 3 days to no resolve. Does anyone know why the Turbo C++ constant calls to my rendering method (code below) will lag in the command prompt but DJGPP will not? I don't know if I'm compiling as debug or not, I don't even know how to check if I am. I did convert the code to ASM and I saw what looked to be debugging data at the header of the source so I don't know...

Any and all comments and help will be greatly appreciated!

Here is a quick example of what I'm up against, simple to compile so please check it out:

#include<stdio.h>
#include<conio.h>
#include<dos.h>
#include<time.h>

#define bX 80
#define bY 24
#define halfX bX/2
#define halfY bY/2
#define resolution bX*bY

#define LEFT 1
#define RIGHT 2

void GameLoop();
void render();
void clearBoard();
void printBoard();
void ballLogic();

typedef struct {
 int x, y;
}vertex;

vertex vertexWith(int x, int y) {
 vertex retVal;
 retVal.x = x;
 retVal.y = y;
 return retVal;
}

vertex vertexFrom(vertex from) {
 vertex retVal;
 retVal.x = from.x;
 retVal.y = from.y;
 return retVal;
}

int direction;

char far *Screen_base;
char *board;
vertex ballPos;

void main() {
 Screen_base = (char far*)0xB8000000;
 ballPos = vertexWith(halfX, halfY);
 direction = LEFT;
 board = (char *)malloc(resolution*sizeof(char));
 GameLoop();
}

void GameLoop() {
 char input;

 clrscr();
 clearBoard();
 do {
  if(kbhit())
   input = getch();
  render();
  ballLogic();

  delay(50);
 }while(input != 'p');
 clrscr();
}

void render() {
 clearBoard();

 board[ballPos.y*bX+ballPos.x] = 'X';

 printBoard();
}

void clearBoard() {
 int d;
 for(d=0;d<resolution;d++)
  board[d] = ' ';
}

void printBoard() {
 int d;

 char far *target = Screen_base+d;
 for(d=0;d<resolution;d++) {
  *target = board[d];
  *(target+1) = LIGHTGRAY;
  ++target;
  ++target;
 }
}

void ballLogic() {
 vertex newPos = vertexFrom(ballPos);

 if(direction == LEFT)
  newPos.x--;
 if(direction == RIGHT)
  newPos.x++;

 if(newPos.x == 0)
  direction = RIGHT;
 else if(newPos.x == bX)
  direction = LEFT;
 else
  ballPos = vertexFrom(newPos);
}
+1  A: 

First, in the code:

void printBoard() {
 int d;

 char far *target = Screen_base+d;  // <-- right there
 for(d=0;d<resolution;d++) {

you are using the variable d before it is initialized.

My assumption is that if you are running this in a DOS window, rather than booting into DOS and running it, is that kbhit is having to do more work (indirectly -- within the DOS box's provided environment) if there isn't already a keypress queued up.

This shouldn't effect your run time very much, but I suggest that in the event that there is no keypress you explicitly set the input to some constant. Also, input should really be an int, not a char.

Other suggestions:

vertexFrom doesn't really do anything.

A = vertexFrom(B);

should be able to be replaced with:

A = B;

Your macro constants that have operators in them should have parenthisis around them.

#define Foo x/2

should be:

#define Foo (x/2)

so that you never ever have to worry about operator precedence no matter what code surrounds uses of Foo.

Under 16 bit x86 PCs there are actually 4 display areas that can be switched between. If you can swap between 2 of those for your animation, and your animations should appear to happen instantaneously. It's called Double Buffering. You have one buffer that acts as the current display buffer and one that is the working buffer. Then when you are satisfied with the working buffer (and the time is right, if you are trying to update the screen at a certain rate) then you swap them. I don't remember how to do this, but the particulars shouldn't be too difficult to find. I'd suggest that you might leave the initial buffer alone and restore back to it upon exit so that the program would leave the screen in just about the state that it started in. Also, you could use the other buffer to hold debug output and then if you held down the space bar or something that buffer could be displayed.

If you don't want to go that route and the 'X' is the only thing changing then you could forgo clearing the screen and just clear the last location of the 'X'.

Isn't the screen buffer an array of 2 byte units -- one for display character, and the other for the attributes? I think so, so I would represent it as an array of:

struct screen_unit {
    char ch;
    unsigned char attr;
}; /* or reverse those if I've got them backwards */

This would make it less likely for you to make mistakes based on offsets.

I'd also probably read and write them to the buffer as the 16 bit value, rather than the byte, though this shouldn't make a big difference.

nategoose
Excellent answer, +1. I agree it is the C-compiler-specific implementation of kbhit() that is causing the problem.
Sanjay Manohar
I fixed the erronous problems with my code, but mostly with the kbhit() calls. In fact I cut them out entirely from my code so that you cannot exit the program via user input alone and I still get the same results. Even with the kbhit() function left out of the source I still get choppy animations that smooth out when I hold a button. I wish I could say that this fixed the problem but unfortunatly it did not. I appreciate your tips for better programming but the issue is still abound!
Parad0x13
I just found out that if I go fullscreen there is no animation lag! What could be the difference?
Parad0x13
I'll bet that you choppy animations aren't from the no-key press situation being slower but faster. So fast that more than one screen redraw happens between the OS actually redrawing the DOS box. It's redrawing it in the middle of your updating it. In full screen your program may actually have real access to the real hardware, so it the OS is not as big of a factor. Increase the delay and see.
nategoose
A: 

I figured out why it wasn't rendering right away, the timer that I created is fine the problem is that the actual clock_t is only accurate to .054547XXX or so and so I could only render at 18fps. The way I would fix this is by using a more accurate clock... which is a whole other story

Parad0x13