Я сделал простую игру с подключением четырех терминалов на 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 ответа
Общие наблюдения
Если вы настоящий новичок, этот код довольно хорош, потому что он использует функции. В частности, main()
вызов game_loop()
это хороший дизайн. Имена функций и имена переменных понятны и облегчают чтение кода.
Код не является переносимым, потому что код вызывает system("cls");
, #include <windows.h>
может быть внутри #ifdef WIN32
windows.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()
сам по себе, но не позволяйте компилятору делать то, что вы можете тривиально сделать самостоятельно с большей ясностью кода.
Харис