простая игра соедини четыре

Я сделал простую игру с подключением четырех терминалов на C и хотел узнать, могу ли я получить отзывы о ней. В основном я хочу знать, где указатели могли бы оптимизировать мой код, и есть ли какие-либо существенные повторения. (Я знаю, что is_occupied функция одна, но я добавил ее для читабельности) Вот код:

/*  simple connect 4 game   */
/*  current version: 1.0    */

// inclusions 
#include <stdio.h>
#include <stdbool.h> 
#include <stdlib.h>
#include <windows.h>
#include <time.h>

// definitions 
#define LEN_X 7                                 // width of a normal board ( I think )
#define LEN_Y 6                                 // length of a normal board ( I think )


// global variables 
char board_red[LEN_Y][LEN_X];                   // the board where the game will be played on 

bool particle_exists_red[LEN_Y][LEN_X];         // a boolean board that holds the slots where red has a piece 
bool particle_exists_yellow[LEN_Y][LEN_X];      // a boolean board that holds the slots where yellow has a piece
bool particle_exists[LEN_Y][LEN_X];             // a boolean board that holds the slots where a piece exists in general
bool temp_particle_exists[LEN_Y][LEN_X];        // a boolean board that holds the value of each slot, for the temp print function
bool temp_particle_exists_red[LEN_Y][LEN_X];    
bool temp_particle_exists_yellow[LEN_Y][LEN_X]; 

int red_wins = 0; 
int yellow_wins = 0; 

bool win = false; 

// protoypes 
void particle_move_red(int x);                  // function in charge of moving the red pieces(particles)
void particle_move_yellow(int x);               // function in charge of moving the yellow pieces(particles)
void print_grid(void);                          // function that prints the finished grid
bool is_occupied(int x, int y);                 // functino used to see if a slot is occupied 
void print_temp_grid(void);                     // function that prints the temporary grid 
bool win_check(void);                           // finction that checks if winconditons are met
void yellow_plays(void);                        // lets the robot make a move 
void game_loop(void);                           // main game loop
void clear_grid(void);                          // function that clears the grid for subsequent games 

// main 
int main(void)
{
    srand(time(NULL)); 

    char keep_playing = 'y'; 
    do
    {
        printf("Do you want to play? (y/n): ");
        scanf(" %c", &keep_playing); 
        if(keep_playing == 'y')
        {
            game_loop(); 
        }
        else 
        {
            printf("You scored:      %d  points\nComputer scored: %d  points\n", red_wins, yellow_wins); 
        }
    } while (keep_playing == 'y');
    
    return 0;
}



// function that moves the red particle 
void particle_move_red(int x)
{
    int move_y = LEN_Y; 
    int i; 
    for(i = LEN_Y; i > 1; i--)
    {
        if(!is_occupied(x,move_y-1))
        {
            move_y--; 
            temp_particle_exists_red[x][move_y] = true; 
            print_temp_grid();
            temp_particle_exists_red[x][move_y] = false; 
            Sleep(60);
            system("cls"); 
        }
    }
    particle_exists[x][move_y] = true; 
    particle_exists_red[x][move_y] = true;
    win_check();
}

// function that moves the yellow particle 
void particle_move_yellow(int x)
{
    int move_y = LEN_Y; 
    int i; 
    for(i = LEN_Y; i > 1; i--)
    {
        if(!is_occupied(x,move_y-1))
        {
            move_y--; 
            temp_particle_exists_yellow[x][move_y] = true; 
            print_temp_grid();
            temp_particle_exists_yellow[x][move_y] = false; 
            Sleep(60);
            system("cls"); 
           
        }
    }
    particle_exists[x][move_y] = true; 
    particle_exists_yellow[x][move_y] = true;
    win_check();
}


// function that checks if a slot is occupied 
bool is_occupied(int x, int y)
{
    return particle_exists[x][y] == true;
}


// function that prints the falling pieces 
void print_temp_grid()
{
    int i, j; 
    printf("\n");
    printf("  |1|2|3|4|5|6|7|\n");
    for(i = LEN_Y; i > 0; i--)
    { 
        printf("  |"); 
        for(j = 1; j <= LEN_X; j++)
        {
            if(temp_particle_exists_red[j][i] == true || particle_exists_red[j][i] == true)
                printf("O|"); 
            else if(temp_particle_exists_yellow[j][i] == true || particle_exists_yellow[j][i] == true) 
                printf("X|"); 
            else
                printf(" |"); 
        }
        printf("\n"); 
    }
}

// function that checks for a win 
bool win_check(void)
{   
    int i, j; 
    for(i = LEN_Y; i > 0; i--)
    {
        for(j = 1; j <= LEN_X; j++)
        {
            // check for red win y axis 
            if (particle_exists_red[j][i] == true) 
                if (particle_exists_red[j][i+1] == true) 
                    if (particle_exists_red[j][i+2] == true)
                        if (particle_exists_red[j][i+3] == true)
                        {
                            printf("Red wins!\n");
                            red_wins++;
                            win = true; 
                            return true; 
                        }
                            

            // check for red win x axis
            if (particle_exists_red[j][i] == true) 
                if (particle_exists_red[j+1][i] == true) 
                    if (particle_exists_red[j+2][i] == true)
                        if (particle_exists_red[j+3][i] == true)
                        {
                            printf("Red wins!\n");
                            red_wins++;
                            win = true;
                            return true;
                        }

            // check for red win oblique from top y 
            if (particle_exists_red[j][i] == true) 
                if (particle_exists_red[j-1][i-1] == true) 
                    if (particle_exists_red[j-2][i-2] == true)
                        if (particle_exists_red[j-3][-3] == true)
                        {
                            printf("Red wins!\n");
                            red_wins++;
                            win = true; 
                            return true;
                        } 

            // check for red win oblique from bottom y 
            if (particle_exists_red[j][i] == true) 
                if (particle_exists_red[j+1][i+1] == true) 
                    if (particle_exists_red[j+2][i+2] == true)
                        if (particle_exists_red[j+3][+3] == true)
                        {
                            printf("Red wins!\n");
                            red_wins++;
                            win = true;
                            return true; 
                        } 
            // check for yellow win y axis 
            if (particle_exists_yellow[j][i] == true) 
                if (particle_exists_yellow[j][i+1] == true) 
                    if (particle_exists_yellow[j][i+2] == true)
                        if (particle_exists_yellow[j][i+3] == true)
                        {
                            printf("yellow wins!\n");
                            yellow_wins++;
                            win = true;
                            return true; 
                        }
                            

            // check for yellow win x axis
            if (particle_exists_yellow[j][i] == true) 
                if (particle_exists_yellow[j+1][i] == true) 
                    if (particle_exists_yellow[j+2][i] == true)
                        if (particle_exists_yellow[j+3][i] == true)
                        {
                            printf("yellow wins!\n");
                            yellow_wins++;
                            win = true;
                            return true;
                        }

            // check for yellow win oblique from top y 
            if (particle_exists_yellow[j][i] == true) 
                if (particle_exists_yellow[j-1][i-1] == true) 
                    if (particle_exists_yellow[j-2][i-2] == true)
                        if (particle_exists_yellow[j-3][-3] == true)
                        {
                            printf("yellow wins!\n");
                            yellow_wins++;
                            win = true; 
                            return true;
                        } 

            // check for yellow win oblique from bottom y 
            if (particle_exists_yellow[j][i] == true) 
                if (particle_exists_yellow[j+1][i+1] == true) 
                    if (particle_exists_yellow[j+2][i+2] == true)
                        if (particle_exists_yellow[j+3][+3] == true)
                        {
                            printf("yellow wins!\n");
                            yellow_wins++;
                            win = true; 
                            return true; 
                        } 
        }
    }
}

void yellow_plays(void)
{
    int randslot = rand() % 6 +1;

    particle_move_yellow(randslot); 

}


// main loop that plays until a win is reached 
void game_loop(void)
{
    int slot;
    win = false;
    clear_grid(); 
    while(!win)
    {   
        print_temp_grid(); 
        printf("Choose a slot to insert piece into(1 to 7): "); 
        scanf("%d", &slot);
        if (slot > 7 || slot < 1)
            printf("value is outside domain\n"); 
        else
        {
            system("cls"); 
            particle_move_red(slot); 
            if (!win)
                yellow_plays();
        }
         
        
    }
    
}

void clear_grid(void)
{
    int i, j;
    for(i = LEN_Y; i > 0; i--)
    {
        for(j = 1; j <= LEN_X; j++)
        {
            particle_exists[j][i] = false; 
            particle_exists_red[j][i] = false; 
            particle_exists_yellow[j][i] = false; 

        }
    }
}

2 ответа
2

Общие наблюдения

Если вы настоящий новичок, этот код довольно хорош, потому что он использует функции. В частности, main() вызов game_loop() это хороший дизайн. Имена функций и имена переменных понятны и облегчают чтение кода.

Код не является переносимым, потому что код вызывает system("cls");, #include <windows.h> может быть внутри #ifdef WIN32windows.h недоступен в системах Linux.

В основном я хочу знать, где указатели могли бы оптимизировать мой код.

Это зависит от того, используете ли вы функцию оптимизации компилятора, маловероятно, что указатели улучшат производительность кода. Если вы не используете оптимизатор, то указатели могли бы улучшить производительность, но это не ясно только из изучения кода.

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

… если есть какие-либо серьезные увольнения.

Есть и другие избыточности из того, что вы упомянули, функции void particle_move_red(int x) и void particle_move_yellow(int x) очень явно избыточны, и если бы было передано больше аргументов, они могли бы быть одной функцией. Еще один способ уменьшить избыточность, если на игровом поле была только одна структура данных, а не несколько структур данных. Это можно было бы сделать, если бы доска состояла из перечислений, представляющих пустое, красное или желтое, а не логическое значение true и false. Использование перечислений может упростить функцию void print_temp_grid() также. Функция bool win_check(void) также содержит избыточность, которую можно уменьшить, каждый логический блок в win_check() может быть вызовом функции.

Как отмечает пользователь ggorlen, появляются предупреждения, когда код компилируется с более высоким уровнем проверки ошибок:

Строка 45: предупреждение C4244: ‘функция’: преобразование из ‘time_t’ в ‘unsigned int’, возможная потеря данных
Строка 248: предупреждение C4715: ‘win_check’: не все пути управления возвращают значение

Второе предупреждение в строке 248 указывает на возможные логические ошибки или ошибки в коде функции. win_check(). Очень интересно, что нет return false; в функции win_check(). Первое предупреждение может быть исправлено с помощью приведения.

Избегайте глобальных переменных

Очень сложно читать, писать, отлаживать и поддерживать программы, использующие глобальные переменные. Глобальные переменные могут быть изменены любой функцией внутри программы, поэтому перед внесением изменений в код требуется проверка каждой функции. В C и C++ глобальные переменные влияют на пространство имен и могут вызывать ошибки компоновки, если они определены в нескольких файлах. ответы на этот вопрос stackoverflow дать более полное объяснение.

Организация кода

Прототипы функций очень полезны в больших программах, содержащих несколько исходных файлов, и это в том случае, если они будут находиться в файлах заголовков. В программе с одним файлом, подобной этой, лучше поместить функцию main() в конец файла, а все функции, которые используются в правильном порядке, над main(). Имейте в виду, что каждая написанная строка кода — это еще одна строка кода, в код которой может залезть ошибка.

Полезные комментарии

Комментарии обычно используются для обозначения проектных решений, за исключением того, что код должен быть самодокументируемым с понятными именами переменных и функций. Вот 4 полезных комментария, которые я вижу:

/*  simple connect 4 game   */
/*  current version: 1.0    */
#define LEN_X 7                                 // width of a normal board ( I think )
#define LEN_Y 6                                 // length of a normal board ( I think )

Если бы определения были BOARD_LENGTH и BOARD_WIDTH второй набор комментариев не потребуется.

Предпочитаю брекеты { и } Вокруг одиночных заявлений в if или loops

Некоторые программисты считают это проблемой стиля, но это значительно упрощает чтение и сопровождение кода, если каждый из них находится в одном месте. if, else или loop блок заключен в фигурные скобки. Расширение функциональности этих операторов может быть проблематичным, если фигурные скобки не используются. Для более подробного обсуждения этого см. Первые 2 ответа на этот Вопрос о переполнении стека. Как указывает один из ответов, это верно для всех C-подобных языков (C, C++, C#, JavaScript, Java и т. д.). Я работал в нескольких компаниях, где это требовалось стандартом кодирования и отмечалось во время проверки кода.

Код в win_check() очень сложно поддерживать, было бы проще, если бы код выглядел так:

            if (particle_exists_red[j][i] == true)
            {
                if (particle_exists_red[j][i + 1] == true)
                {
                    if (particle_exists_red[j][i + 2] == true)
                    {
                        if (particle_exists_red[j][i + 3] == true)
                        {
                            printf("Red wins!\n");
                            red_wins++;
                            win = true;
                            return true;
                        }
                    }
                }
            }

Добавить охрану:

В верхней части файла для нестандартной функции sleep() /Sleep():

#ifdef _WIN32
    #include <Windows.h>
#else
    #include <unistd.h>
#endif

С помощью макросов вы можете определить, используете ли вы Windows или Unix-подобную систему, и вызвать соответствующую функцию в зависимости от вашей текущей платформы:

static inline void clear_screen (void)
{
    #if defined(__linux__) || defined(__unix__) || defined(__APPLE__)
        system ("clear");
    #endif

    #if defined(_WIN32) 
        system ("cls");
    #endif
} 

Или макрос:

#if defined(__linux__) || defined(__unix__) || defined(__APPLE__) 
    #define CLEAR_SCREEN() system ("clear") 
#else
    #define CLEAR_SCREEN() system ("cls")
#endif

То же самое можно было бы сделать о Sleep():

static inline void snooze (unsigned int time) 
{
    #ifdef _WIN32
        Sleep (time);
    #else
        usleep (time) */
    #endif

    /* Or you could define a macro. */
}

Обратите внимание, что POSIX.1-2001 объявляет эту функцию устаревшей; использовать nanosleep(2) вместо. POSIX.1-2008 удалил спецификацию usleep().


Проверьте возврат системных/библиотечных вызовов:

scanf() возвращает количество успешно выполненных преобразований. Мы должны проверить его возврат, чтобы убедиться, что мы действительно прочитали int:

// scanf("%d", &slot);

if (scanf ("%d", &slot) != 1) {
    complain();
}

Является ли приведенное выше решение надежным? Нет.

Видеть: Как правильно читать int? и
Руководство для начинающих scanf().


Уменьшить масштаб:

Начиная с C99 переменные можно объявлять в for управляющее выражение цикла.

#if 0
    int i; 
        for(i = LEN_Y; i > 1; i--)
#else 
    for (int i = LEN_Y; i > 1; i--) {}
#endif

Обратите внимание, что i является локальным для for только блок цикла и перестанет существовать, как только выйдет за пределы области действия.


Упростить код:

/*
if (particle_exists_red[j][i] == true) 
    if (particle_exists_red[j-1][i-1] == true) 
        if (particle_exists_red[j-2][i-2] == true)
            if (particle_exists_red[j-3][-3] == true)
            {
                printf("Red wins!\n");
                red_wins++;
                win = true; 
                return true;
            } 
*/
 
if (particle_exists_red[j][i] 
    && particle_exists_red[j-1][i-1]
    && particle_exists_red[j-2][i-2] 
    && particle_exists_red[j-3][-3]) {
    printf("Red wins!\n");
    red_wins++;
    return win = true;
}

// Or:

bool status = particle_exists_red[j][i] 
              && particle_exists_red[j-1][i-1]
              && particle_exists_red[j-2][i-2] 
              && particle_exists_red[j-3][-3];

if (status) { 
    printf("Red wins!\n");
    red_wins++;
    return win = true;
}

Незначительный:

  • Используйте беззнаковые типы для мощностей, порядковых номеров и размеров. (Например, индексы массива никогда не могут быть отрицательными.)
  • Предпочитать return EXIT_SUCCESS к return 0.
  • Предпочитать puts() к printf() когда возможности синтаксического анализа последнего не требуются. Да, компилятор действительно может заменить вызов на puts() сам по себе, но не позволяйте компилятору делать то, что вы можете тривиально сделать самостоятельно с большей ясностью кода.

Делиться

Улучшить этот ответ

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *