Продолжаю писать игру "Змейка"

В функции, где происходит считывание символов с клавиатуры (input()), чтобы передвинуть объект (называемой "змея") в другое место по оси x или y, который обозначен в моем коде, как символ '0' -- необходимо все время прожимать клавишиу 'enter', то есть: ввел символ передвижения (w,s,a,d) --> ввел клавишу 'enter' чтобы функция потока ввода считала мои данные и далее, оператор ветвления switch бы начал свою работу. Мне же, как и всем другим пользователям, которые пишут свои игры, необходимо, чтобы можно было передвигать объект БЕЗ постоянного нажатия клавиши 'enter', ведь это, как минимум не нормально, не говоря уж об удобстве и т.п. Как осуществить это?

Я слышал про библиотеку <conio.h>, которая позволяет осуществлять подобные вещи. Почитав несколько часов ее документацию -- я понял, что можно считывать символ (переменная operation в функции input()) не используя функцию ввода scanf(), а используя _getch(). И да, стало быть все нормально, я действительно больше не нажимаю постоянно клавишу 'enter' при вводе символа передвижения, но увы, у меня перестали пропадать яблоки, когда бы мое тело ('0') доходило до них.

И да, еще хочу добавить, что тело, которое передвигается расположено сверху, а не снизу (то есть, при вводе символа (w,s,a,d) выводиться сразу два поля (как бы карта, где можно увидеть gameplay), в первом поле же ничего нет, а если взглянуть чуть повыше, то вы сможете увидеть это тело, которое передвигается при нажатии клавиши). Эту проблему я пытался решить несколько часов, но так и не понял, в чем дело (upd. до сих продолжаю).

В общем , проблем куча, потому я и обращаюсь за помощью.

Вот мой код:

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

#define UP      1
#define DOWN    2
#define LEFT    3
#define RIGHT   4

#define STOP    100


#define HEIGHT  10
#define WIDTH   20

int picture[HEIGHT][WIDTH];

int input(int, int);
void printSnake(void);
void initialization(void){
    extern int picture[HEIGHT][WIDTH];
    for(int index1 = 0; index1 < HEIGHT; index1++)
        for(int index2 = 0; index2 < WIDTH; index2++) 
            picture[index1][index2] = ' ';
}
void apples(void){
    extern int picture[HEIGHT][WIDTH];

    picture[HEIGHT-4][WIDTH-4] = '.';
    picture[HEIGHT-8][WIDTH-10] = '.';
}
int draw(int x, int y){
    
    extern int picture[HEIGHT][WIDTH];

    initialization();
    
    int i = 0 , j = 0;
    for(int i = 0; i < HEIGHT; i++){
        for(int j = 0; j < WIDTH; j++){
            if(i == 0)picture[i][j] = '#';
            if(i == HEIGHT - 1) picture[i][j] = '#';
            if(j == WIDTH - 1) picture[i][j] = '|';        
        }
        picture[i][j] = '\n';
    }

    for(int a = 0; a < HEIGHT; a++)
        picture[a][1] = '|';
    
    // for(int i = 0; i < HEIGHT; i++){
    //     for(int j = 0; j < WIDTH){
    //         if(picture[x] == '|' || picture[x][] == )
    //     }
    // }
    if(picture[x][y] == '|' || picture[x][y] == '#'){
        return 0;
    }
    return 1;
}


// 1
void draw_apple_1(void){
    extern int picture[HEIGHT][WIDTH];

    picture[HEIGHT-4][WIDTH-4] = '.';
}
void apple_1(int not_apple){
    if(not_apple == 0)
        draw_apple_1();
}

// 2
void draw_apple_2(void){
    extern int picture[HEIGHT][WIDTH];

    picture[HEIGHT-6][WIDTH-10] = '.';
}
void apple_2(int not_apple){
    if(not_apple == 0)
        draw_apple_2();
}

// 3
void draw_apple_3(void){
    extern int picture[HEIGHT][WIDTH];

    picture[HEIGHT-8][WIDTH-10] = '.';
}
void apple_3(int not_apple){
    if(not_apple == 0)
        draw_apple_3();
}



int input(int x, int y){
    extern int picture[HEIGHT][WIDTH];

    char operation;

    printf("%s\n","Enter (UP,DOWN,RIGHT,LEFT):");
    scanf("%c",&operation);
    switch (operation)
    {
    case 'w':
        picture[--x][y] = '0';
        return UP;
    case 's':
        picture[++x][y] = '0';
        return DOWN;
    case 'a':
        picture[x][--y] = '0';
        return LEFT;
    case 'd':
        picture[x][++y] = '0';
        return RIGHT;
    case 'f':
        return STOP;
    default:
        return 0;
    }
}
void printSnake(void){
    extern int picture[HEIGHT][WIDTH];
    for(int index1 = 0; index1 < HEIGHT; index1++)
        for(int index2 = 0; index2 < WIDTH; index2++)
            printf("%c",picture[index1][index2]); 
}


int check(int x, int y){
    extern int picture[HEIGHT][WIDTH];

    for(int i = 0; i < HEIGHT; i++){
        for(int j = 0; j < WIDTH; j++){
            if(picture[i][j] == '.' && picture[x][y] == '.'){
                return 1;
            }
        }
    }
    return 0;
}

int check_in_snake(int x, int y){
    extern int picture[HEIGHT][WIDTH];

    if(picture[x][y] == '.'){
        return 1;
    }
    return 0;
}


void increase(int x, int y){
    extern int picture[HEIGHT][WIDTH];
    picture[x][--y] = '0';
}

int main(void){
    int count_all_apples = 0;

    int conflict = 0;

    int is_all_apple = 0;

    int x = 5, y = 9;

    int not_apple_1 = 0;
    int not_apple_2 = 0;
    int not_apple_3 = 0;
    
    while(1){
        printf("\nx: %i y: %i\n", x,y);
        if(!draw(x,y))
        {
            conflict = 1;
            break;
        }

        apple_1(not_apple_1);
        apple_2(not_apple_2);
        apple_3(not_apple_3);

        printf("\nThe number of apples you have: %i\n",count_all_apples);

        int cnt = input(x, y);
        if (cnt == UP) --x;
        else if (cnt == DOWN) ++x;
        else if (cnt == LEFT) --y;
        else if (cnt == RIGHT) ++y;
        else if (cnt == STOP) break;
        
        int flag = check(x,y);
        if(flag){
            count_all_apples++;
            printf("%s\n","Apple is FIND");
            if(x == HEIGHT-4 && y == WIDTH-4) not_apple_1 = 1;

            if(x == HEIGHT-6 && y == WIDTH-10) not_apple_2 = 1;

            if(x == HEIGHT-8 && y == WIDTH-10) not_apple_3 = 1;

        } else if(!flag) printf("%s\n","Apple is NOT FIND");


        int sn = check_in_snake(x,y);
        if(sn == 1){
        
            printf("%s\n","It's time to lengthen the snake");
            increase(x,y);
            
        } 
        else {
            printf("%s\n","Don't lengthen the snake yet.");
        }


        printSnake();
        
        if(count_all_apples == 3){
            is_all_apple = 1;
            break;
        }

    }
    system("cls");
    printf("%s\n","-------------------------------------------------------");
    if(is_all_apple){printf("%s\n","You have collected all the apples.");}
    else if(conflict == 1){
        printf("%s\n", "The snake collided with the wall.");
    }
    printf("\n%s","Game Over.");
    printf("\n%s\n","-------------------------------------------------------");
    return 0;
}

p.s. Не нужно осуждать, что "игра" написана отвратительно. Я это сам прекрасно понимаю, но и вы поймите, что змея -- это мой самый первый пет-проект, да и в целом я в программировании всего пару месяцев.


Ответы (1 шт):

Автор решения: Solt

Попробую ещё раз указать на недочёты.

int picture[HEIGHT][WIDTH];

если массив предназначен для хранения символов, он не должен быть int, он должен быть char. Это не только сэкономит место, но и позволит использовать блочные операции, а не посимвольные. Например вывод на экран одной printf, а не циклом. Для этого будет удобно ширину увеличить на 1 и добавить в конец каждой строки '\n'.

extern int picture[HEIGHT][WIDTH]; - бесполезная инструкция, предназначенная для подключения ресурсов, расположенных в других модулях.

initialization(); - бесполезная функция, зря затирающая весь массив.

int draw(int x, int y){
    //extern int picture[HEIGHT][WIDTH];

    //initialization();
    
    int i = 0 , j = 0;
    for(int i = 0; i < HEIGHT; i++){
        for(int j = 0; j < WIDTH; j++){
            if(i == 0) picture[i][j] = '#';
            else if(i == HEIGHT - 1) picture[i][j] = '#';
            else if(j == WIDTH - 1) picture[i][j] = '|';
            // Эта строка заменяет функцию initialization();
            else picture[i][j] = ' '; 
        }

        picture[i][j] = '\n';
        //после выхода из цикла j содержит WIDTH, что за пределами массива
        // либо picture[i][WIDTH-1] = '\n';
        // либо увеличить массив выше до picture[HEIGHT][WIDTH+1]
    }
    
    //По какой причине этот цикл сделан отдельно?
    //Достаточно было в предыдущем сделать аналогичную проверку
    //else if(j == 1) picture[a][1] = '|';
    for(int a = 0; a < HEIGHT; a++)
        picture[a][1] = '|';
    
    // Это единственное, что по большому счёту надо оставить в этой функции,
    // а постоянную перерисовку рамок и пустоты вынести в initialization()
    // и вызвать один раз при старте программы
    if(picture[x][y] == '|' || picture[x][y] == '#'){
        return 0;
    }
    return 1;
}

Далее идут мелкие функции, отрисовывающие яблоки. Если функция вызывается только из одного места и представляет из себя одну команду, напрашивается вопрос, а нужна ли она вообще? В частности, отрисовку яблок можно также переложить на инициализацию и сделать один раз. Кроме того, сами яблоки можно сложить в массив с координатами и в цикле отрисовать всё сразу.

Определение наезда на яблоко тоже не должно требовать очередного указания его координат. Во-первых оно отрисовано на поле в соответствующих координатах, во-вторых, как я и советовал, яблоки могут быть в отдельном массиве. Имея под рукой x и y их легко найти там.

Теперь input(); Тут тоже проделываются одинаковые действия по изменению координат один раз тут, другой раз там, где вызывается. Можно x и y передавать по ссылке и изменять сами координаты а в качестве возврата оставить только признак выхода:

//объявление
int input(int* x, int* y){
...
//вызов
if(input(&x, &y))
    break;

Функция check() тоже какая-то бессмысленная. Зачем обходить двойным циклом всё поле, искать на нем яблоки и проверять наличие их же по имеющимся координатам? Не достаточно разве одной единственной проверки if(picture[x][y] == '.') и всё?

Функция printSnake тоже сводится к единственному printf("%s",(char*)picture);, если массив будет оформлен, как я указывал ранее.

А как только придёт понимание, что саму змейку придётся хранить в массиве (возможно циклическом, чтоб не переписывать постоянно) с координатами каждого её элемента, то станет понятно, что перерисовывание всего поля станет бессмысленным вовсе. Достаточно научиться переставлять курсор в терминале в нужные координаты и отрисовывать только змею по месту.

И последнее. Текущие координаты x и y прям напрашиваются стать глобальными и доступными во всех функциях сразу.

Ну, пока хватит. Спасибо за внимание.

→ Ссылка