Крестики-нолики для человека в C


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

#include <stdio.h>
#include <stdbool.h>
#include <stdlib.h>

#define WIDTH 3
#define SIZE WIDTH * WIDTH
#define MOVE_COUNT 8

const char *DIVIDER = "\n---+---+---\n";
const char *NUM_STR = "123456789";

const char EMPTY = ' ';
const char PLAYER1 = 'X';
const char PLAYER2 = 'O';

/**
 * The container of all the positions that must
 * be occupied by a player to score a win.
 */
const size_t WINNING_MOVES[MOVE_COUNT][3] = {
        // Horizontal streaks
        {0, 1, 2},
        {3, 4, 5},
        {6, 7, 8},
        // Vertical streaks
        {0, 3, 6},
        {1, 4, 7},
        {2, 5, 8},
        // Diagonal streaks
        {0, 4, 8},
        {2, 4, 6}

};

typedef struct state_t {
    char values[SIZE];
    struct state_t *next;
} State;

/**
 * @return A new State with all blanks and navigation cleared.
 */
State *new_state() {
    State *state = malloc(sizeof(State));
    for (size_t i = 0; i < SIZE; ++i) {
        state->values[i] = EMPTY;
    }
    state->next = NULL;
}

/**
 * @param state The parent state.
 * @param index The (free) position to mark.
 * @param player The player which sill mark the cell.
 * @return A new state from a parent with the requested
 * position marked by the given player mark.
 */
State *derive(State *state, size_t index, char player) {
    state->next = new_state();
    // Copy values
    for (size_t i = 0; i < SIZE; ++i) {
        state->next->values[i] = state->values[i];
    }
    state = state->next;
    state->values[index] = player;

    return state;
}

/**
 * Frees the space occupied by the given State and its
 * successors.
 * @param state The state to deallocate.
 */
void free_state(State *state) {
    if (state == NULL)
        return;
    free(state->values);
    free(state->next);
    state->next = NULL;
    free(state);
}

/**
 * Pretty prints the given state with evenly spaced
 * symbols and dividers and indices in place of blanks.
 * @param state The state to be pretty-printed.
 */
void print_state(State *state) {
    for (size_t row = 0; row < WIDTH; ++row) {
        if (row != 0) { printf(DIVIDER); }
        for (size_t col = 0; col < WIDTH; ++col) {
            if (col != 0) { printf("|"); }
            int index = row * WIDTH + col;
            char value = state->values[index];
            printf(" %c ", (value == EMPTY ? NUM_STR[index] : value));
        }
    }
    printf("\n\n");
}

/**
 * @param player The player who's win to check.
 * @param state The state to check for win in.
 * @return true if the player has won.
 */
bool has_won(char player, State *state) {
    for (size_t move = 0; move < MOVE_COUNT; ++move) {
        int count = 0;
        for (size_t index = 0; index < WIDTH; ++index) {
            size_t move_idx = WINNING_MOVES[move][index];
            if (state->values[move_idx] == player) {
                count++;
            } else { // no point in checking any further for this move
                break;
            }
        }
        if (count == WIDTH) {
            print_state(state);
            printf("\n%c wins!!\n", player);
            return true;
        }
    }
    return false;
}

/**
 * Creates a new state from the given state using input
 * provided by the human player.
 *
 * @param player The character designated to the human player.
 * @param current The current state to move from.
 * @return A new state based on the input given by the human player.
 */
State *take_input(char player, State *current) {
    printf("%c's turn!\n", player);
    print_state(current);
    printf("\nEnter the position you want to mark : ");
    size_t index;
    scanf("%d", &index); // NOLINT

    while (index < 1 || index > SIZE || current->values[index - 1] != EMPTY) {
        printf("Invalid position! Try again : ");
        scanf("%d", &index); // NOLINT
    }

    return derive(current, index - 1, player);
}

int main() {
    State *state = new_state();
    State *head = state;
    char player = PLAYER1;

    while (!has_won(PLAYER1, state) && !has_won(PLAYER2, state)) {
        state = take_input(player, state);

        // Change player
        player = player == PLAYER1 ? PLAYER2 : PLAYER1;
    }
    free_state(head);
    return 0;
}

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



728
3
задан 25 января 2018 в 06:01 Источник Поделиться
Комментарии
2 ответа

эта функция:

State *new_state() {
State *state = malloc(sizeof(State));
for (size_t i = 0; i < SIZE; ++i) {
state->values[i] = EMPTY;
}
state->next = NULL;
}

есть подпись, которая утверждает, что она возвращает указатель на State. Однако, нет return state; заявление в код.
Предлагаю изменить подпись функции:

void new_state()


по поводу высказывания, как:

scanf("%d", &index); // NOLINT


  1. объявления index это size_t которая не что иное, как int таким образом, входной спецификатор формата должен быть похож на: %lu. (существует также специальный спецификатор формата для size_t однако, это будет сделать.\


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

scanf("%d", &index); // NOLINT

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

Примечание: scanf() семейство функций не устанавливает errno когда некоторые описатель формата не

Предлагаю :

if( 1 != scanf("%d", &index) )
{
fprintf( stderr, "scanf for the next location to place a token failed" );
}


для облегчения читаемости и понимания: следовать аксиоме: только один оператор на строке и (в большинстве) только одно объявление переменной в сообщении.

так это:

if (row != 0) { printf(DIVIDER); }

должно быть написано как:

if( row )
{
printf( DIVIDER );
}


Сжимая все блоки кода вместе, без разделения делает код гораздо более сложна для понимания, отладки и т. д. Предлагаю: отдельные блоки кода (for if else while do...while switch case default ) через одну пустую строку.


в отношении:

void free_state(State *state) {
if (state == NULL)
return;
free(state->values);
free(state->next);
state->next = NULL;
free(state);
}

это возвращает только одно "государство" до кучи. Тем не менее, эти государства находятся в связанном списке. так должно быть в цикле, возвращая память, выделяемая для каждого государства, пока "далее" поле имеет значение null.


при вызове любой из функций выделения памяти в куче: (Танос, метода calloc, realloc) всегда проверяйте (!=Значение null), возвращаемое значение, чтобы гарантировать, что операция прошла успешно.


в функции: has_won(), предлагаю:

bool done = false;
for (size_t move = 0; !done && move < MOVE_COUNT; ++move)
{
int count = 0;
for (size_t index = 0; index < WIDTH; ++index)
{
size_t move_idx = WINNING_MOVES[move][index];
if (state->values[move_idx] == player)
{
count++;
}

else
{ // no point in checking any further for this move
break;
}
}

if (count == WIDTH)
{
done = true;
}
}

if( done )
{
print_state(state);
printf("\n%c wins!!\n", player);
}

return done;

5
ответ дан 25 января 2018 в 09:01 Источник Поделиться

Проверить результат функции ввода/вывода

Этот код может не инициализировать index:

int index;
scanf("%d", &index); // NOLINT

while (index < 1 || index > SIZE || current->values[index - 1] != EMPTY) {
printf("Invalid position! Try again : ");
scanf("%d", &index); // NOLINT
}

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

size_t index;
int nread;
while ((nread = scanf("%zd", &index)) != 1)
|| !index || index > SIZE
|| current->values[index - 1] != EMPTY)
{
if (nread == EOF) {
/* serious error - indicate failure */
return NULL;
}
printf("Invalid position! Try again : ");
}

Очевидно, вы должны будете адаптироваться main() для обработки возврат null из take_input().

3
ответ дан 25 января 2018 в 09:01 Источник Поделиться