Бесконечный цикл пользовательского ввода в крестики-нолики при вводе алфавитного символа

#c #arrays #console-application #doubly-linked-list

#c #массивы #консольное приложение #двусвязный список

Вопрос:

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

Это работает так, как должно, но у меня проблема, когда, если пользователь вводит алфавитный символ, программа просто повторяет цикл, говоря, что это не число, когда проверяет мою GetHumanMove функцию. Кто-нибудь знает, как заставить его прекратить это делать и просто сказать, что это не число, один раз, не зацикливая программу?

Вот мой код:

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

//Tic-Tac-Toe game in C
//20/03/2019
//5x5 Board with 4 in a row to win. Also has playable AI that randomly blocks human moves. 
//Also has replay function using a doubly linked list

/*
int board[49] = {
    ;,;, ;, ;, ;, ;, ;,
    ;,1, 2, 3, 4, 5 ,;,
    ;,6, 7, 8, 9, 10,;,
    ;,11,12,13,14,15,;,
    ;,16,17,18,19,20,;,
    ;,21,22,23,24,25,;,
    ;,;, ;, ;, ;, ;, ;,
}
*/

struct node
{
    int side;
    int move;
    int place;
    struct node * prev;
    struct node * next;
};

void append(struct node **, int, int, int);
void display(struct node *);
void delete(struct node **, int);

//Constants of pieces, border and empty places
enum { NOUGHTS, CROSSES, BORDER, EMPTY };
//Constants for human/computer win or draw
enum { HUMANWIN, COMPWIN, DRAW };

const int directions[4] = { 1, 7, 6, 8 };

int moveCount = 0;

//Values of original places to play
const int ConvertTo49[25] = {
    8, 9, 10,11,12,
    15,16,17,18,19,
    22,23,24,25,26,
    29,30,31,32,33,
    36,37,38,39,40
};

void append(struct node ** list, int side, int move, int place)
{
    struct node *temp , *current = *list;

    if(*list == NULL)
    {
        *list = (struct node *) malloc(sizeof(struct node));
        (*list) -> prev = NULL;
        (*list) -> side = side;
        (*list) -> move = move;
        (*list) -> place = place;
        (*list) -> next = NULL;
    }
    else
    {
        while(current -> next != NULL)
            current = current -> next;

        temp = (struct node *) malloc(sizeof(struct node));
        temp -> side = side;
        temp -> move = move;
        temp -> place = place;
        temp -> next = NULL;
        temp -> prev = current;
        current -> next = temp;
    }
}

void delete(struct node ** list, int num)
{
    struct node *temp = *list;

    while(temp != NULL)
    {
        if(temp -> move == num)
        {
            if(temp == *list)
            {
                *list = (*list) -> next;
                (*list) -> prev = NULL;
            }
            else
            {
                if(temp -> next == NULL)
                    temp -> prev -> next = NULL;
                else
                {
                    temp -> prev -> next = temp -> next;
                    temp -> next -> prev = temp -> prev;
                }
                free(temp);
            }
            return ;
        }
        temp = temp -> next;
    }
    printf("Element %d not found in the supplied list nn", num);
}

//Loop through a direction until a border square is hit
int GetNumForDir(int startSq, const int dir, const int *board, const int us)
{
    int found = 0;

    while(board[startSq] != BORDER)
    {
        if(board[startSq] != us)
        {
            break;
        }
        found  ;
        startSq  = dir;
    }
    return found;
}

//Loop through directions and find four in a row
int FindFourInARow(const int *board, const int ourIndex, const int us)
{
    int dirIndex = 0;
    int dir = 0;
    int countFour = 1;

    for(dirIndex = 0; dirIndex < 4;   dirIndex)
    {
        dir = directions[dirIndex];
        countFour  = GetNumForDir(ourIndex   dir, dir, board, us);
        countFour  = GetNumForDir(ourIndex   dir * -1, dir * -1, board, us);
        if(countFour == 4)
        {
            break;
        }
        countFour = 1;
    }
    return countFour;
}

//Set up board structure
void InitialiseBoard(int *board)
{
    //index variable
    int index;

    //set whole board to border squares initially
    for(index = 0; index < 49;   index)
    {
        board[index] = BORDER;
    }

    //Set up empty squares for placing pieces
    for(index = 0; index < 25;   index)
    {
        board[ConvertTo49[index]] = EMPTY;
    }
}

//Print the board
void PrintBoard(const int *board)
{
    //index variable
    int index;

    //Set pieces and places
    char pceChars[] = "OX| ";

    printf("nnn");

    //Loop through and only draw the actual playable squares, leaving out the border squares
    for(index = 0; index < 25;   index)
    {
        //Put lines inbetween places
        if(index==1 || index==2 || index==3 || index==4 ||
           index==6 || index==7 || index==8 || index==9 ||
           index==11 || index==12 || index==13 || index==14 ||
           index==16 || index==17 || index==18 || index==19 ||
           index==21 || index==22 || index==23 || index==24)
        {
            printf(" |");
        }

        //Put space at start to space out board from edge of screen
        if(index==0)
        {
            printf("        ");
        }

        //Print new line every 5 places
        if(index!= 0 amp;amp; index==5 || index==10 || index==15 || index==20)
        {
            printf("n");
            printf("        -------------------");
            printf("n");
            printf("        ");
        }

        //Print each playable piece
        printf(" %c", pceChars[board[ConvertTo49[index]]]);     
    }
    printf("nn");
}

//Check if board has empty spaces
int HasEmpty(const int *board)
{
    int index;

    for(index = 0; index < 25;   index)
    {
        if(board[ConvertTo49[index]] == EMPTY) return 1;
    }

    return 0;
}

//Function for making move of current player
void MakeMove(int *board, const int sq, const side)
{   
    //set board place that either player decides
    board[sq] = side;   
}

void UndoMove(int *board, int sq, int side)
{   
    //set board place that either player decides
    board[sq] = side;   
}


//Help the computer find a winning move
int GetWinningMove(int *board, const int side)
{
    int ourMove = -1;
    int winFound = 0;
    int index = 0;

    for(index = 0; index < 25;   index)
    {
        if(board[ConvertTo49[index]] == EMPTY)
        {
            ourMove = ConvertTo49[index];
            board[ourMove] = side;

            if(FindFourInARow(board, ourMove, side) == 4)
            {
                winFound = 1;
            }
            board[ourMove] = EMPTY;
            if(winFound == 1)
            {
                return ourMove;
            }
            ourMove = -1;
        };
    }
    return ourMove;
}

//Get computer player move
int GetComputerMove(int *board, const int side)
{
    int index;
    int numFree = 0;
    int availableMoves[25];
    int randMove = 0;

    //Set random number to randomly run a function
    int randFunction = 0;
    randFunction = (rand() % 2);

    //Go for the winning move
    randMove = GetWinningMove(board, side);
    if(randMove != -1)
    {
        return randMove;
    }

    //If random function is 1, stop any winning move from the human
    if(randFunction == 1)
    {
        randMove = GetWinningMove(board, side ^ 1);
        if(randMove != -1)
        {
            return randMove;
        }
    }

    randMove = 0;
    //Loop through all squares and put piece in random place
    for(index = 0; index < 25;   index)
    {
        if(board[ConvertTo49[index]] == EMPTY)
        {
            availableMoves[numFree  ] = ConvertTo49[index];
        };
    }

    randMove = (rand() % numFree);
    return availableMoves[randMove];
}


//Get human player move
int GetHumanMove(const int *board)
{
    //Array for user input
    int userInput;
    char term;
    //Start moveOK at 0
    int moveOK = 0;
    int move = -1;

    //Loop through until move being made is valid
    while(moveOK == 0)
    {

        printf("n");

        //Ask user for input of 1-9
        printf("Please enter a place on the board to make your move, from 1 to 25: ");
        //Make sure that user doesnt enter long string with number on the end that eventaully passes tests in the while loop
        scanf("%d%c", amp;userInput, amp;term);

        if(term != 'n')
        {
            move = -1;
            printf("This is not a numbern");
            continue;
          }

        //Check if input is in proper range
        if(userInput < 1 || userInput > 25)
        {
            move = -1;
            printf("Invalid Rangen");
            continue;
        }

        //decrement move to get the array location of the playable places
        userInput--;

        //Check if place selected is already taken
        if(board[ConvertTo49[userInput]] != EMPTY)
        {
            move = -1;
            printf("Square not availablen");
            continue;
        }

        //Set move ok to 1 after passing all the tests
        moveOK = 1;
    }

    //Print move being made and return it
    printf("nHuman making Move at square: %dn",(userInput 1));
    return ConvertTo49[userInput];
}

//Run Game function
void RunGame()
{
    struct node *list;
    list = NULL;

        //Display
    printf("nn     Noughts And Crosses Game:");
    printf("n     =========================");

    //game over variable
    int gameOver = 0;   
    //Start on noughts side
    int side = NOUGHTS; 
    //Record last move
    int lastMoveMade = 0;   
    //Create new board
    int board[49];
    //Count of first move
    int initialMove = 0;

    //Initialise the Board
    InitialiseBoard(amp;board[0]);

    //Print the board to screen
    PrintBoard(amp;board[0]);

    //While game isn't over
    while(!gameOver)
    {
        initialMove  ;
        //Human
        if(side==NOUGHTS)
        {
            //Ask to undo move after first move played
            if(initialMove > 1)
            {
                while(list -> prev != NULL)
                        list = list -> prev;
                int undo;
                printf("nnWould you like to undo your move? nEnter 2 to undo or any other key/s to continue: ");
                scanf("%d", amp;undo);
                if( undo == 2)
                {
                    while(list -> next != NULL)
                        list = list -> next;

                    char pceChars[] = "OX| ";
                    //printf(" %c", pceChars[board[ConvertTo49[index]]]);

                    int count = 2;
                    while(count != 0)
                    {                       
                        UndoMove(amp;board[0], list -> place, EMPTY);                      
                        list = list -> prev;
                        delete(amp;list, list -> next -> move);
                        moveCount--;
                        count--;
                    }
                    PrintBoard(amp;board[0]);
                }
            }
                //Make new move for human
                lastMoveMade = GetHumanMove(amp;board[0]);
                MakeMove(amp;board[0], lastMoveMade, side);
                moveCount  ;
                int humanMove;
                //Find the position the computer is making on the 5x5 board
                for(humanMove = 0; humanMove < 25;   humanMove)
                {
                    if(ConvertTo49[humanMove] == lastMoveMade)
                    {
                        break;
                    }
                }               
                append(amp;list, 0, moveCount, lastMoveMade);
                side = CROSSES;

        }
        //Computer
        else
        {
            //Make move for computer
            lastMoveMade = GetComputerMove(amp;board[0], side);            
            MakeMove(amp;board[0], lastMoveMade, side);
            int compMove;
            //Find the position the computer is making on the 5x5 board
            for(compMove = 0; compMove < 25;   compMove)
            {
                if(ConvertTo49[compMove] == lastMoveMade)
                {
                    break;
                }
            }
            printf("nComputer making move at square: %d", compMove 1);
            moveCount  ;
            append(amp;list, 1, moveCount, lastMoveMade);          
            side = NOUGHTS;
            PrintBoard(amp;board[0]);
        }

        //If three in a row exists, game over
        if(FindFourInARow(board, lastMoveMade, side ^ 1) == 4)
        {                       
            gameOver = 1;
            if(side == NOUGHTS)
            {
                printf("nGame Over!n");
                printf("Computer Wins! :(n");
            }
            else
            {               
                PrintBoard(amp;board[0]);
                printf("nGame Over!n");
                printf("Human Wins! :Dn");
            }
        }

        //If no more moves, game is a draw      
        if(!HasEmpty(board))
        {
            PrintBoard(amp;board[0]);
            printf("nnGame Over!n");
            gameOver = 1;
            printf("It's a draw! :/n");
        }
    }

    /*
    *
    *    Match Replay
    *   
    */
    int answer; 
    printf("nnWould you like to replay the match? nEnter 1 to replay or any other key/s to quit: ");
    scanf("%d", amp;answer);

    if(answer == 1)
    {
        InitialiseBoard(amp;board[0]);
        while ( list != NULL )
        {

            MakeMove(amp;board[0], list -> place, list -> side);   
            PrintBoard(amp;board[0]);
            char who[7];
            if(list -> side == 0)
            {
                strcpy(who, "NOUGHTS");
                //who = "NOUGHTS";
            }
            else
            {
                strcpy(who, "CROSSES");
                //who = "CROSSES";
            }
            int moveMade;
            //Find the position the computer is making on the 5x5 board
            for(moveMade = 0; moveMade < 25;   moveMade)
            {
                if(ConvertTo49[moveMade] == list -> place)
                {
                    break;
                }
            }
            printf ("nMove:-    Side:%8s    Place: %dn", list -> move, who, moveMade 1);
            Sleep(2000);
            list = list -> next ;
        }
    }
    else
    {
        printf("nGood Game! See ya!n");
        getch();
    }   
}



int main()
{
    //Create random number
    srand(time(NULL));

    //Call Run Game to play
    RunGame();

    return 0;
}
  

Комментарии:

1. Пользователь вводит алфавитный символ для %d ? Вы не проверили возвращаемое значение ни у одного из scanf ов? Он будет оставаться в буфере до тех пор, пока вы его не удалите. Проще получить строку ввода с помощью fgets и применить sscanf , а затем, если тесты ввода завершатся неудачей (включая отсутствующую, но необходимую проверку «количество отсканированных элементов»), просто введите другую строку.

2. Мне удалось остановить цикл с помощью fgets. Но после того, как я решил не отменять ход, сделанный путем ввода ключа, который не является тем, который он запрашивает. Когда я перехожу к следующей части, которая просит меня ввести номер места, которое я хочу воспроизвести снова, появляется сообщение об ошибке о том, что это не номер один раз, а затем позволяет мне продолжить. Итак, это работает, но по какой-то причине выдает мне одну небольшую ошибку, как будто она принимает клавишу ввода при выборе поворота

Ответ №1:

Ввод, который не соответствует вашему формату, не используется scanf . Он остается во входном потоке. Поэтому, когда вы вводите букву, она не соответствует нужному номеру %d и остается во входном потоке.

 scanf("%d%c", amp;userInput, amp;term);
  

Вместо scanf было бы проще использовать getline для использования всей строки ввода. А затем проанализируйте результат с помощью sscanf (строковой версии функции) или другого метода.

 char *line = NULL;
size_t len = 0;

while ((read = getline(amp;line, amp;len, stdin)) != -1) {
    // Do something with line

    int scanned = sscanf(line, "%d%c", amp;userInput);

    if (scanned == EOF || scanned != 2) {
        printf("Line failed to matchn");
    }
}
  

Ответ №2:

Просто прочитайте страницу руководства:

    The  format  string consists of a sequence of directives which describe
   how to process the sequence of input characters.  If  processing  of  a
   directive  fails,  no  further  input  is read, and scanf() returns.  A
   "failure" can be either of the following: input failure,  meaning  that
   input  characters  were  unavailable, or matching failure, meaning that
   the input was inappropriate (see below).
  

Вы не проверяете возвращаемое значение scanf() . Таким образом, вы все время пытаетесь сканировать одну и ту же цифру в качестве digit.