gotoxy in game condign with C

148 Views Asked by At

I am currently developing a Duck Hunt-style game with C language for a university class, but I'm having a problem that I can't move the player's aim while keeping the targets moving, for some reason, I can only move them separately. Here is a condensed version of the code, I would be very glad if someone could help.

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

#define Projetil 207
#define ESC 27

                    //formato da parabola (fazer ainda)
                    //substituir o sleep
                    
int cx = 55;
int cy = 25;

void atirarpcima(){
        int q;
        q = cy+1;
        while (q!=0){
        gotoxy(cx+4, q);
        printf("%c", Projetil);
        Sleep(37);     
        gotoxy(cx+4, q);
        printf("  ");
        q--;
    }
}



void pacman(){
    gotoxy(cx, cy);
    printf(" *     *");
    gotoxy(cx, cy+1);
    printf("***   ***");
    gotoxy(cx, cy+2);
    printf("**** ****");
    gotoxy(cx, cy+3);
    printf(" *******");
    
}

void fantasmasaida(){
    
    srand(time(NULL));
    int y = (rand() % 9)+1;
    int x = (rand() % 7)+14;
    while(x<100){
    gotoxy(x, y);
    printf(" ****** ");
    gotoxy(x, y+1);
    printf("** ** **");
    gotoxy(x, y+2);
    printf("********");
    gotoxy(x, y+3);
    printf("** ** **"); 
    
    gotoxy(x, y);
    printf("        ");
    gotoxy(x, y+1);
    printf("        ");
    gotoxy(x, y+2);
    printf("        ");
    gotoxy(x, y+3);
    printf("        "); 
    x++;
    }
   
    
    
}

void gotoxy(int x, int y)
{
    COORD coord;
    coord.X = x;
    coord.Y = y;
    SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), coord);
}

int main(){
system("mode con: lines=30 cols=120");
char tecla;
    
pacman();
while (1){
    while(!kbhit()){     
    fantasmasaida();
    }
    tecla = getch();
    if (tecla == 'q'){
    atirarpcima();
    } 
    if (tecla == ESC){
        break;
        }
    
}

return 0;
    }
1

There are 1 best solutions below

0
Joshua On

Ok here we go video game development 101

The normal way to do this really is single threaded code; however a loop per thing displayed (sprite) to animate is wrong, as you've already learned. I really wish they taught this in the old books at entry level. I always wanted to know how as a teen and couldn't find out. There are three basic concepts.

To start off with, we have the master loop. Almost everything goes in the master loop as follows:

struct {
    int old_x;
    int old_y;
    int old_state;
    int x;
    int y;
    int state;
} atirarpcima;

struct {
    int old_x;
    int old_y;
    int old_state;
    int x;
    int y;
    int state;
} pacman;

struct {
    int old_x;
    int old_y;
    int old_state;
    int x;
    int y;
    int state;
} fantasmasaida;

void update_atirarpcima()
{
    atirarpcima.x = atirarpcima.old_x;
    atirarpcima.y = atirarpcima.old_y;
    atirarpcima.x = ... update location
    atirarpcima.y = ... update location
}

void erase_atirarpcima()
{
    gotoxy(atirarpcima.old_x, atirarpcima.old_y);
    printf(" "); //...
}

void draw_atirarpcima()
{
    gotoxy(atirarpcima.old_x, atirarpcima.old_y);
    printf("."); //...
}

void erase_pacman()
{
   // ...
}

void draw_pacman()
{
   // ...
}

void erase_fantasmasaida()
{
   //...
}

void draw_fantasmasaida()
{
   //...
}

void game_loop()
{
    do {
        uint32_t elapsed = GetTickCount();
        // Update game state
        update_atirarpcima();
        update_pacman();
        update_fantasmasaida();
        // Erase the old sprites
        erase_atirarpcima();
        erase_pacman();
        erase_fantasmasaida();
        // Draw the new ones
        draw_atirarpcima();
        draw_pacman();
        draw_fantasmasaida();
        if (_kbhit()) {
            /* do something with keyboard */
        }
        Sleep(16 - (GetTickCount() - Elapsed()); // About 60FPS
    } while (!gameover)
}

Now everything moves at once, but the game's impossible because the target jumps around too much. We need to fix that.

void update_fantasmasaida()
{
    if (state-- == 0) {
        fantasmasaida.old_x = x;
        fantasmasaida.old_y = y;
        fantasmasaida.x = (rand() % 9)+1;
        fantasmasaida.y = .(rand() % 7)+14;
        state = 60 + rand() % 60;
    }
}

That's the easy one out of the way.

You will now notice that it takes waaay too long for a shot to cross the field. We will get to that, but first we need to clean up some repeated junk and at the same time get rid of the ridiculous hardcoding of the objects on the screen (hereafter known as sprites).

struct sprite {
    void (*update_sprite)(struct sprite *sprite);
    void (*erase_sprite)(struct sprite *sprite);
    void (*draw_sprite)(struct sprite *sprite);
    int old_x;
    int old_y;
    int x;
    int y;
};

#define GAMESPEED 16
#define MAX_SPRITES 8
struct sprite game_sprites[MAX_SPRITES];

sprite *alloc_sprite()
{
    for (int i = 0; i < MAX_SPRITES; i++)
        if (game_sprites[i].draw_sprite == NULL) {
            memset(game_sprites[i], 0, sizeof(struct sprite));
            return game_sprites[i];
        }
    return NULL;
}

void free_sprite(struct sprite *sprite)
{
    if (!sprite) return;
    sprite->draw_sprite = NULL;
}

void game_init()
{
    struct sprite *pacman = alloc_sprite();
    pacman->x = ...
    pacman->y = ...
    pacman->old_x = pacman->x;
    pacman->old_y = pacman->y;
    pacman->update_sprite = update_pacman;
    pacman->erase_sprite = erase_pacman;
    pacman->draw_sprite = draw_pacman;
    // Same for fantasmasaida
    // but don't do atirarpcima here it's dynamic (and multiple)
}

// All the update/erase/draw routines need to take their slot now:
void update_fantasmasaida(struct sprite *sprite)
{
    if (sprite->state-- == 0) {
        sprite->old_x = x;
        sprite->old_y = y;
        sprite->x = (rand() % 9)+1;
        sprite->y = .(rand() % 7)+14;
        sprite->state = 60 + rand() % 60;
    }
}


void game_loop()
{
    do {
        uint32_t elapsed = GetTickCount();
        for (int i = 0; i < MAX_SPRITES; i++) {
             if (sprites[i].draw_sprite) {
                 sprites[i]->update_sprite(&sprites[i]);
             }
        }
        for (int i = 0; i < MAX_SPRITES; i++) {
             if (sprites[i].draw_sprite) {
                 sprites[i]->erase_sprite(&sprites[i]);
             }
        }
        for (int i = 0; i < MAX_SPRITES; i++) {
             if (sprites[i].draw_sprite) {
                 sprites[i]->draw_sprite(&sprites[i]);
             }
        }
        if (_kbhit()) {
            /* do something with keyboard */
        }
        Sleep(16 - (GetTickCount() - Elapsed()); // About 60FPS
    } while (!gameover)
}

Now for the speed problem. The best fix is to track the position in units smaller than display units. We edit the struct as follows:

struct sprite { void (*update_sprite)(struct sprite *sprite); void (*erase_sprite)(struct sprite *sprite); void (*draw_sprite)(struct sprite *sprite); int old_x; int old_y; int sp_x; int sp_y; int x; int y; };

Your code that moves it at some velocity would have looked something like this:

     sprite->x += dx;    
     sprite->y += dx;

But now we do the following:

int sign(int x)
{
    if (x > 0) return 1;
    if (x < 0) return -1;
    return 0;
}

void apply_deltav(struct sprite *sprite, int dx1000, int dy1000)
{
    sprite->sub_x += dx1000;
    if (abs(sprite->subx > 1000)) {
        sprite->x += sprite->sub_x / 1000;
        sprite->sub_x = sign(sprite->subx) * (abs(sprite->sub_x) % 1000);
    }
    sprite->sub_y += dy1000;
    if (abs(sprite->suby > 1000)) {
        sprite->y += sprite->sub_y / 1000;
        sprite->sub_x = sign(sprite->suby) * (abs(sprite->sub_y) % 1000);
    }
}

And we give the apply_deltav in units a thousand times smaller than the display unit. (Historically this was 256 but that's because 8 bit CPUs.)

Now if you tune your constants it's smooth.

It gets deeper. If you keep going you will encounter non-solid background, overlapping sprites, z-order, etc. But this is enough to get started.