3
\$\begingroup\$

An assignment this past semester was to write functions such that the following C program would run:

int main()
{
   int board[BOARD_SIZE_HORIZ][BOARD_SIZE_VERT] = { {0} };
   int player_num, computer_num;
   int last_move; 

   /* Ask Alice if she wants to go first */
   player_num = print_welcome();
   if (player_num == 1) computer_num = 2;
   else computer_num = 1;

   /* If Alice wants to go first, let her make a move */
   if (player_num == 1)
   {
      display_board(board);
      last_move = player_move(board,player_num);
      display_board(board);
   }

   while (1)
   {
      /* Make a computer move, then display the board */
      last_move = best_move(board,computer_num);
      printf("Computer moved in column: %d\n", last_move);
      display_board(board);

      /* Check whether the computer has won */
      if (check_win_or_tie(board,last_move)) return 0;


      /* Let Alice make a move, then display the board */
      last_move = player_move(board,player_num);
      display_board(board);

      /* Check whether Alice has won */
      if (check_win_or_tie(board,last_move)) return 0;
   }
}

Writing functions so the computer would play against the player was easy, but the computer just made a random move (obviously too easy). For extra credit, we could write a function (+sub-functions as needed) to have the computer make the 'best' move in a situation. I put some work into this, and my 'AI' would make fairly good moves in many situations, but my code came in 17th of 24 when played against the other extra-credit programs.

The full code is quite long- I'll post it at the very end so you can see everything

My approach in the best_move function was: First, check whether there are any moves that would win the game, and make the move (either to win or block the player from winning):

int check3win(int board[][BOARD_SIZE_VERT], int computer_num)
{
    int winMove;
    int blockMove;
    int playerNum;

    playerNum = (computer_num == 1 ? 2 : 1);

    if (check3_vertwin(board, computer_num) != 8) {
        winMove = check3_vertwin(board, computer_num);
        return winMove;
    }
    if (check3_horizwin(board, computer_num) != 8) {
        winMove = check3_horizwin(board, computer_num);
        return winMove;
    }
    if (check3_diagRUwin(board, computer_num) != 8) {
        winMove = check3_diagRUwin(board, computer_num);
        return winMove;
    }
    if (check3_diagRDwin(board, computer_num) != 8) {
        winMove = check3_diagRDwin(board, computer_num);
        return winMove;
    }
    if (check3_vertwin(board, playerNum) != 8) {
        blockMove = check3_vertwin(board, playerNum);
        return blockMove;
    }
    if (check3_horizwin(board, playerNum) != 8) {
        blockMove = check3_horizwin(board, playerNum);
        return blockMove;
    }
    if (check3_diagRUwin(board, playerNum) != 8) {
        blockMove = check3_diagRUwin(board, playerNum);
        return blockMove;
    }
    if (check3_diagRDwin(board, playerNum) != 8) {
        blockMove = check3_diagRDwin(board, playerNum);
        return blockMove;
    }
    return 8;
}

(The check3_***** functions each check whether there are 3 in a row anywhere in the board, for the four types of wins - horizontal, vertical diagonal Right-Down, diagonal Right-Up)

If not, the computer will use 'build2', which checks whether there are moves to make that would create 3 in a row:

int build2(int board[][BOARD_SIZE_VERT], int computer_num)
{
    int build2;

    if (build2_diagDN(board,computer_num) != 8)
    {
        build2 = build2_diagDN(board,computer_num);
        return build2;
    }

    if (build2_diagUP(board,computer_num) != 8)
    {
        build2 = build2_diagUP(board,computer_num);
        return build2;
    }
    if (build2vert(board,computer_num) != 8)
    {
        build2 = build2vert(board,computer_num);
        return build2;
    }
    if (build2horiz(board,computer_num) != 8)
    {
        build2 = build2horiz(board,computer_num);
        return build2;
    }
    return 8;
}

The functions I have written do what they're supposed to do, but I quickly realized there's gotta be a more straightforward way to have the program make a good move, not just conditionals upon conditionals checking for wins, blocks, trying to build 3 in a row, trying to build 2 in a row...

I'd like to see if there are better ways to go about this.

Note(s): The other 'similar' questions I could find were asking about programs for player-versus-player versions, i.e. no 'AI' portion at all, or were for different languages that I don't know.

Here is the full code for each of the three files -

Functions file c4_functions.c (this is what I had to write)

#include "connect4_functions.h"
#include <ctype.h>
#include <sys/types.h>

/* Forward function declarations  */

bool check_diagRD     (int board[][BOARD_SIZE_VERT], int m);
bool check_diagRU     (int board[][BOARD_SIZE_VERT], int m);
bool check_vertical   (int board[][BOARD_SIZE_VERT], int m);
bool check_horizontal (int board[][BOARD_SIZE_VERT], int m);
int  build2           (int board[][BOARD_SIZE_VERT], int computer_num);
int  build2_diagDN    (int board[][BOARD_SIZE_VERT], int computer_num);
int  build2_diagUP    (int board[][BOARD_SIZE_VERT], int computer_num);
int  build2horiz      (int board[][BOARD_SIZE_VERT], int computer_num);
int  build2vert       (int board[][BOARD_SIZE_VERT], int computer_num);
int  check3win        (int board[][BOARD_SIZE_VERT], int computer_num);
int  check3_diagRDwin (int board[][BOARD_SIZE_VERT], int computer_num);
int  check3_diagRUwin (int board[][BOARD_SIZE_VERT], int computer_num);
int  check3_horizwin  (int board[][BOARD_SIZE_VERT], int computer_num);
int  check3_vertwin   (int board[][BOARD_SIZE_VERT], int computer_num);




/*   Function print_welcome()  */
int print_welcome(void)
{

    char c;

    /* Seed the rand() function to be called from random_move. 
       Since connect4.c can't be changed & doesn't include a seed,
       it's included here, as this function is only called once
       and prior to all calls to random_move (and thus to rand() */
    srand(time(NULL));


    printf("\n*** Welcome to the Connect-Four game!!! ***\n");
    printf("Would you like to make the first move [y/n]: \n");

    c = getchar();

    while (getchar() != '\n') { } //clears stdin

    if (c ==    'n' || c == 'N') return 2;
    else return 1;

}

/*  Function display_board()  */
void display_board(int board[] [BOARD_SIZE_VERT])
{

    int r = 0;
    int c = 0;

    printf("\n");
    for (r = 0; r < BOARD_SIZE_VERT; ++r)
    {
        printf ("+");

        for (c = 0; c < BOARD_SIZE_HORIZ; ++c) printf("---+");

        printf ("\n|");

        for (c = 0; c < BOARD_SIZE_HORIZ; ++c)
        {
            switch (board[c][r])
            {
                case 0:
                    printf("   |");
                    break;

                case 1:
                    printf(" X |");
                    break;

                case 2:
                    printf(" O |");
                    break;
                default:
                    printf("Error: board entry %d,%d invaid.\n",r,c);
                    break;
            }
        }
        printf("\n");
    }

    printf ("+");
    for (c = 0; c < BOARD_SIZE_HORIZ; ++c)
        printf("---+");
    printf ("\n  1   2   3   4   5   6   7  \n\n");
    return;
}

/*  Function random_move()  */

int random_move(int board[][BOARD_SIZE_VERT], int computer_num)
{
    bool valid = false;
    int m = 0;

    while (!valid)
    {
        m = (rand() % BOARD_SIZE_HORIZ) + 1;

        valid = !is_column_full(board,m);
    }
    update_board(board, m, computer_num);
    return m;
}
/*  Function player_move()  */
int player_move(int board[][BOARD_SIZE_VERT], int player_num)
{
    char c = 'c';  //Initializes c to a non-digit so isdigit(c) returns false
    int x = 0;

    /* the loop will repeat until the player enters a digit */
    while (!isdigit(c))
    {
        printf("Please enter your move: ");

        c = getchar();

        while (getchar() != '\n') { } //clears stdin

        /* x is the ASCII value of c; if c represents a digit, subtracting '0' *
         * will convert the character to the integer it represents.            */
        x = c - '0';

        //Checks whether x is valid, i.e. between 1 and 8
        if (x < 1 || x > 8)
        {
            printf("Not a valid move. Enter a column number!\n");
            c = 'c';       //reset c to a non-digit to continue looping
                           //in case the user entered a digit that was invalid
        }

        else
        {
            if (is_column_full(board,x))
            {
                printf("This column is full. Try again!.\n");
                c = 'c';
            }
            update_board(board, x, player_num);
        }
    }
    return x;
}

/*  Function check_win_or_tie()  */
bool check_win_or_tie(int board[][BOARD_SIZE_VERT], int last_move)
{
    int w;
    int c;
    int r = 0;

    w = check_winner(board, last_move);
    if (check_winner(board, last_move))
    {
        printf("*****************************\n");
        printf("* Player %c won!!! Game over *\n", (w == 1 ? 'X' : 'O'));
        printf("*****************************\n");
        return true;
    }
    else
    {
        for (c = 0; c < BOARD_SIZE_VERT; c++)
        {
            if (board[c][r] == 0) return false;
        }
        printf("*****************************\n");
        printf("* Game is a tie!! No winner *\n");
        printf("*****************************\n");
        return true;
    }
}

/*  Function is_column_full()  */
bool is_column_full(int board[][BOARD_SIZE_VERT], int m)
{
    if (board[m-1][0] == 0)
        return false;

    else
        return true;
}

/*  Function update_board()  */
void update_board(int board[][BOARD_SIZE_VERT], int m, int player_num)
{

    int c, i;

    // convert the column number to the array index for that column
    c = m - 1;

    for (i = BOARD_SIZE_VERT-1; i >= 0; --i)
    {
        if (board[c][i] != 0) continue;
        else
        {
            board[c][i] = player_num;
            return;
        }
    }
}


/*  Function check_winner  */
int check_winner(int board[][BOARD_SIZE_VERT], int last_move)
{
    int c;
    int i;

    //again, converts column of last_move to that column's array index
    c = last_move - 1;

    if (check_diagRU    (board, last_move) == true ||
        check_diagRD    (board, last_move) == true ||
        check_horizontal(board, last_move) == true ||
        check_vertical  (board, last_move) == true )
    {
        for (i = 0; i < BOARD_SIZE_VERT; i++)
        {
            switch (board[c][i])
            {
                case 0:
                    continue;
                case 1:
                    return 1;
                case 2:
                    return 2;
                default:
                    printf("Error! Value in board[%d][%d] invalid.\n",c,i);
                    return 0;
            }
        }
    }
    return 0;
}

/*  Function check_diagRD()  */
bool check_diagRD(int board[][BOARD_SIZE_VERT], int last_move)
{
    int r;  //row index
    int c;  //column index

    for (r = 0; r < 3; r++)
    {
        for (c = 0; c < 4; c++)
        {
            if (board[c][r] != 0               &&
                board[c][r] == board[c+1][r+1] &&
                board[c][r] == board[c+2][r+2] &&
                board[c][r] == board[c+3][r+3])
            {
                return true;
            }
            else continue;
        }
    }
    return false;
}

/*  Function check_diagRU()  */
bool check_diagRU(int board[][BOARD_SIZE_VERT], int last_move)
{
    int r;  //row index
    int c;  //column index

    for (r = 3; r < BOARD_SIZE_VERT; r++)
    {
        for (c = 0; c < 4; c++)
        {
            if (board[c][r] != 0               &&
                board[c][r] == board[c+1][r-1] &&
                board[c][r] == board[c+2][r-2] &&
                board[c][r] == board[c+3][r-3])
            {
                return true;
            }
            else continue;
        }
    }
    return false;
}

/*  Function check_ vertical()  */

bool check_vertical(int board[][BOARD_SIZE_VERT], int m)
{
    int r;  //row index
    int c;  //column index

    for (r = 0; r < 3; r++)
    {
        for (c = 0; c < BOARD_SIZE_HORIZ; c++)
        {
            if (board[c][r] != 0             &&
                board[c][r] == board[c][r+1] &&
                board[c][r] == board[c][r+2] &&
                board[c][r] == board[c][r+3])
            {
                return true;
            }
            else continue;
        }
    }
    return false;
}

/*  Function check_horizontal() checks for 4 in a row  */

bool check_horizontal(int board[][BOARD_SIZE_VERT], int m)
{
    int r;  //row index
    int c;  //column index

    for (r = 0; r < BOARD_SIZE_VERT; r++)
    {
        for (c = 0; c < 4; c++)
        {
            if (board[c][r] != 0             &&
                board[c][r] == board[c+1][r] &&
                board[c][r] == board[c+2][r] &&
                board[c][r] == board[c+3][r])
            {
                return true;
            }
            else continue;
        }
    }
    return false;
}

/*  Function best_move() - To be used in extra credit competition  */
int  best_move(int board[][BOARD_SIZE_VERT], int computer_num)
{
    int playerNum;
    int bestMove;

    playerNum = (computer_num == 1 ? 2 : 1);

    if (check3win(board, computer_num) != 8)
    {
        bestMove = check3win(board, computer_num);
        update_board(board, bestMove, computer_num);
        return bestMove;
    }
    else if (build2(board,computer_num) != 8)
    {
        bestMove = build2(board, computer_num);
        update_board(board, bestMove, computer_num);
        return bestMove;
    }
/*    else if (build1(board,computer_num) != 8)
    {
        bestMove = build1(board,computer_num);
        update_board(board, bestMove, computer_num);
        return bestMove;
    }*/
    else bestMove = random_move(board, computer_num);

    return bestMove;
}

/*  Function build2()  */
int build2(int board[][BOARD_SIZE_VERT], int computer_num)
{
    int build2;

    if (build2_diagDN(board,computer_num) != 8)
    {
        build2 = build2_diagDN(board,computer_num);
        return build2;
    }

    if (build2_diagUP(board,computer_num) != 8)
    {
        build2 = build2_diagUP(board,computer_num);
        return build2;
    }
    if (build2vert(board,computer_num) != 8)
    {
        build2 = build2vert(board,computer_num);
        return build2;
    }
    if (build2horiz(board,computer_num) != 8)
    {
        build2 = build2horiz(board,computer_num);
        return build2;
    }
    return 8;
}

/*  Function build2_diagDN()  */
int build2_diagDN(int board[][BOARD_SIZE_VERT], int computer_num)
{
    int c;
    int r;

    for (r = 0; r < 4; r++)
    {
        for (c = 0; c < 4; c++)
        {
            //check X X [] []
            if (board[c][r]     == computer_num  &&
                board[c+1][r+1] == computer_num  &&
                board[c+3][r+3] == 0             &&
                board[c+2][r+2] == 0)
            {
                if (is_column_full(board,c+3)) continue;

                if (r == 3 || board[c+2][r+1] != 0) return c+3;
            }

            //check [] [] X X
            if (board[c+2][r+2]  == computer_num  &&
                board[c+3][r+3]  == computer_num  &&
                board[c][r]      == 0             &&
                board[c+1][r+1]  == 0             &&
                board[c+1][r+2]  != 0)
            {
                if (is_column_full(board,c+2)) continue;

                return c+2;
            }

            //check [] X X []
            if (board[c+2][r+2]  == computer_num  &&
                board[c+1][r+1]  == computer_num  &&
                board[c][r]      == 0             &&
                board[c+3][r+3]  == 0)
            {
                if (is_column_full(board,c+4)) continue;

                if (r == 3 || board[c+3][r+3]  != 0) return c+4;
            }

            //check [] X [] X
            if (board[c+1][r+1]  == computer_num  &&
                board[c+3][r+3]  == computer_num  &&
                board[c][r]      == 0             &&
                board[c+2][r+2]  == 0             &&
                board[c][r+1]    != 0)
            {
                if (is_column_full(board,c+1)) continue;

                return c+1;
            }

            //check X [] X []
            if (board[c+2][r+2]  == computer_num  &&
                board[c][r]      == computer_num  &&
                board[c+1][r+1]  == 0             &&
                board[c+3][r+3]  == 0)
            {
                if (is_column_full(board,c+4)) continue;

                if (r == 3 || board[c+3][r+3]  != 0) return c+4;
            }

            //check X [] [] X
            if (board[c][r+1]    == computer_num  &&
                board[c+3][r+3]  == computer_num  &&
                board[c+1][r+1]  == 0             &&
                board[c+2][r+2]  == 0             &&
                board[c+2][r+3]  != 0)
            {
                if (is_column_full(board,c+3)) continue;

                return c+3;
            }
        }
    }
    return 8;
}


/*  Function build2_diagUP()  */
int build2_diagUP(int board[][BOARD_SIZE_VERT], int computer_num)
{
    int c;
    int r;

    for (r = 3; r < BOARD_SIZE_VERT; r++)
    {
        for (c = 0; c < 4; c++)
        {
            //check X X [] []
            if (board[c][r]      == computer_num  &&
                board[c+1][r-1]  == computer_num  &&
                board[c+3][r-3]  == 0             &&
                board[c+2][r-2]  == 0             &&
                board[c+3][r-2]  != 0)

                return c+4;

            //check [] [] X X
            if (board[c+2][r-2]  == computer_num  &&
                board[c+3][r-3]  == computer_num  &&
                board[c][r]      == 0             &&
                board[c+1][r-1]  == 0)
            {
                if (r == BOARD_SIZE_VERT || board[c][r+1] != 0) return c+1;
            }

            //check [] X X []
            if (board[c+2][r-2]  == computer_num  &&
                board[c+1][r-1]  == computer_num  &&
                board[c][r]      == 0             &&
                board[c+3][r-3]  == 0             &&
                board[c+3][r-2]  != 0)

                return c+4;

            //check [] X [] X
            if (board[c+1][r-1]  == computer_num  &&
                board[c+3][r-3]  == computer_num  &&
                board[c][r]      == 0             &&
                board[c+2][r-2]  == 0)
            {
                if (r == BOARD_SIZE_VERT || board[c][r+1] != 0)  return c+1;
            }

            //check X [] X []
            if (board[c+2][r-2]  == computer_num  &&
                board[c][r]      == computer_num  &&
                board[c+1][r-1]  == 0             &&
                board[c+3][r-3]  == 0             &&
                board[c+3][r-2]  != 0)

                return c+4;

            //check X [] [] X
            if (board[c][r+1]    == computer_num  &&
                board[c+3][r-3]  == computer_num  &&
                board[c+1][r-1]  == 0             &&
                board[c+2][r-2]  == 0             &&
                board[c+1][r-2]  != 0)

                return c+2;
        }
    }
    return 8;
}


/*  Function build2vert()  */
int build2vert(int board[][BOARD_SIZE_VERT], int computer_num)
{
    int c;
    int r;

    for (r = 2; r < BOARD_SIZE_VERT; r++)
    {
        for (c = 0; c < BOARD_SIZE_HORIZ; c++)
        {
            if (is_column_full(board,c))
            {
                printf("column %d full. Skipping\n", c);
                continue;
            }
            if (board[c][r-1]  == 0            &&
                board[c][r]    == computer_num &&
                board[c][r+1]  == computer_num)
            {
                return c+1;
            }
        }
    }
    return 8;
}


/*  Function build2horiz()  */
int build2horiz(int board[][BOARD_SIZE_VERT], int computer_num)
{
    int c;
    int r;

    for (r = 5; r >= 0; r--)
    {
        for (c = 0; c < 4; c++)
        {
            //check X X [] []
            if (board[c+3][r]  == 0             &&
                board[c+2][r]  == 0             &&
                board[c+1][r]  == computer_num  &&
                board[c][r]    == computer_num)
            {
                if (r == 5 || board[c+3][r+1] != 0) return c+4;
            }
            //check [] [] X X
            if (board[c][r]    == 0             &&
                board[c+1][r]  == 0             &&
                board[c+2][r]  == computer_num  &&
                board[c+3][r]  == computer_num)
            {
                if (r == 5 || board[c][r+1] != 0) return c+1;
            }
            //check [] X X []
            if (board[c][r]    == 0             &&
                board[c+3][r]  == 0             &&
                board[c+2][r]  == computer_num  &&
                board[c+1][r]  == computer_num)
            {
                if (r == 5 || board[c+1][r+1] != 0) return c+1;
            }

            //check X [] X []
            if (board[c+3][r]  == 0             &&
                board[c+1][r]  == 0             &&
                board[c+2][r]  == computer_num  &&
                board[c][r]    == computer_num)
            {
                if (r == 5 || board[c+3][r+1] != 0) return c+4;
            }
            //check [] X [] X
            if (board[c][r]    == 0             &&
                board[c+2][r]  == 0             &&
                board[c+1][r]  == computer_num  &&
                board[c+3][r]  == computer_num)
            {
                if (r == 5 || board[c][r+1] != 0) return c+1;
            }
            //check X [] [] X
            if (board[c+1][r]  == 0             &&
                board[c+2][r]  == 0             &&
                board[c][r]    == computer_num  &&
                board[c+3][r]  == computer_num)
            {
                if (r == 5 || board[c+1][r+1] != 0) return c+2;
            }
        }
    }
    return 8;
}


/*  Function check3win()  */
int check3win(int board[][BOARD_SIZE_VERT], int computer_num)
{
    int winMove;
    int blockMove;
    int playerNum;

    playerNum = (computer_num == 1 ? 2 : 1);

    if (check3_vertwin(board, computer_num) != 8) {
        winMove = check3_vertwin(board, computer_num);
        return winMove;
    }
    if (check3_horizwin(board, computer_num) != 8) {
        winMove = check3_horizwin(board, computer_num);
        return winMove;
    }
    if (check3_diagRUwin(board, computer_num) != 8) {
        winMove = check3_diagRUwin(board, computer_num);
        return winMove;
    }
    if (check3_diagRDwin(board, computer_num) != 8) {
        winMove = check3_diagRDwin(board, computer_num);
        return winMove;
    }
    if (check3_vertwin(board, playerNum) != 8) {
        blockMove = check3_vertwin(board, playerNum);
        return blockMove;
    }
    if (check3_horizwin(board, playerNum) != 8) {
        blockMove = check3_horizwin(board, playerNum);
        return blockMove;
    }
    if (check3_diagRUwin(board, playerNum) != 8) {
        blockMove = check3_diagRUwin(board, playerNum);
        return blockMove;
    }
    if (check3_diagRDwin(board, playerNum) != 8) {
        blockMove = check3_diagRDwin(board, playerNum);
        return blockMove;
    }
    return 8;
}
/*  Function check3_vertwin()  */
int check3_vertwin(int board[][BOARD_SIZE_VERT], int computer_num)
{
    int c; //column index
    int r; //row index

    //start checking from 5th row; can't make a move if top row full
    for (r = 1; r < 4; r++)
    {
        for (c = 0; c < BOARD_SIZE_HORIZ; c++)
        {
            if (is_column_full(board,c))
            {
                printf("Column %d full. Skipping\n", c);
                continue;
            }

            if (board[c][r-1]   == 0                 &&
                board[c][r]     == computer_num      &&
                board[c][r+1]   == computer_num      &&
                board[c][r+2]   == computer_num)
            {
                return c+1; //changed from c
            }
        }
    }
    return 8;
}
/*  Function check3_horizwin()  */
int check3_horizwin(int board[][BOARD_SIZE_VERT], int computer_num)
{
    int c; //column index
    int r; //row index

    for (r = 5; r >= 0; r--)
    {
        for (c = 0; c < 5; c++)
        {
            //check X X X []
            if (board[c+3][r]  == 0                 &&
                board[c][r]    == computer_num      &&
                board[c+1][r]  == computer_num      &&
                board[c+2][r]  == computer_num)
            {
                if (r == 5 || board[c+3][r+1] != 0) return c+4; //changed from 3
            }
            //check X [] X X
            if (board[c+1][r] == 0                 &&
                board[c][r]   == computer_num      &&
                board[c+2][r] == computer_num      &&
                board[c+3][r] == computer_num)
            {
                if (r == 5 || board[c+1][r+1] != 0) return c+2;  //changed from 1
            }
            //check X X [] X
            if (board[c+2][r]   == 0                 &&
                board[c][r]     == computer_num      &&
                board[c+1][r]   == computer_num      &&
                board[c+3][r]   == computer_num)
            {
                if (r == 5 || board[c+2][r+1] != 0) return c+3; //changed from 2
            }
            //check [] X X X
            if (board[c][r]   == 0                 &&
                board[c+1][r] == computer_num      &&
                board[c+2][r] == computer_num      &&
                board[c+3][r] == computer_num)
            {
                if (r == 5 || board[c][r+1] != 0) return c+1; //changed from 3
            }
        }
    }
    return 8;
}

/*  Function check3_diagRDwin()  */
int check3_diagRDwin(int board[][BOARD_SIZE_VERT], int computer_num)
{
    int c; //column index
    int r; //row index

    for (r = 0; r < 4; r++)
    {
        for (c = 0; c < 4; c++)
        {
            //check X X X []
            if (board[c+3][r+3] == 0            &&
                board[c][r]     == computer_num &&
                board[c+1][r+1] == computer_num &&
                board[c+2][r+2] == computer_num)
            {
                if (r == 3 || board[c+3][r+4] == 1 || board[c+3][r+4] == 2) return c+4;
            }

            //check X X [] X
            if (board[c+2][r+2] == 0            &&
                board[c][r]     == computer_num &&
                board[c+1][r+1] == computer_num &&
                board[c+3][r+3] == computer_num &&
                board[c+2][r+3] != 0 )
            {
                return c+3;
            }
            //check X [] X X
            if (board[c+1][r+1] == 0            &&
                board[c][r]     == computer_num &&
                board[c+2][r+2] == computer_num &&
                board[c+3][r+3] == computer_num &&
                board[c+1][r+2] != 0)
            {
                return c+2;
            }
            //check [] X X X
            if (board[c][r]     == 0                 &&
                board[c+1][r+1] == computer_num      &&
                board[c+2][r+2] == computer_num      &&
                board[c+3][r+3] == computer_num)
            {
                if (r == 5 || board[c][r+1] != 0) return c+1; //changed from 3
            }
        }
    }
    return 8;
}
/*  Function check3_diagRUwin()  */
int check3_diagRUwin(int board[][BOARD_SIZE_VERT], int computer_num)
{
    int c; //column index
    int r; //row index

    for (r = 3; r < BOARD_SIZE_VERT; r++)
    {
        for (c = 0; c < 4; c++)
        {
            //check X X X []
            if (board[c+3][r-3] == 0            &&
                board[c][r]     == computer_num &&
                board[c+1][r-1] == computer_num &&
                board[c+2][r-2] == computer_num &&
                board[c+3][r-2] != 0)
            {
                return c+4; //changed from 3?
            }

            //check X X [] X
            if (board[c+2][r-2] == 0            &&
                board[c][r]     == computer_num &&
                board[c+1][r-1] == computer_num &&
                board[c+3][r-3] == computer_num &&
                board[c+2][r-1] != 0)
            {
                return c+3;  //changed from 2?
            }
            //check X [] X X
            if (board[c+1][r-1] == 0            &&
                board[c][r]     == computer_num &&
                board[c+2][r-2] == computer_num &&
                board[c+3][r-3] == computer_num &&
                board[c+1][r] != 0)
            {
                return c+2;  //changed from 1?
            }
            //check [] X X X
            if (board[c][r]     == 0                 &&
                board[c+1][r+1] == computer_num      &&
                board[c+2][r+2] == computer_num      &&
                board[c+3][r+3] == computer_num)
            {
                if (r == 5 || board[c][r+1] != 0) return c+1; //changed from 3
            }
        }
    }
    return 8;
}


/*  End of file  */

Header file - connect4_functions.h (provided by the assignment - just some #includes, #defines, and forward declarations of the functions we were required to write [the other declarations are in c4_functions.c)

#ifndef CONNECT4_FUNCTIONS
#define CONNECT4_FUNCTIONS


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



#define BOARD_SIZE_HORIZ 7
#define BOARD_SIZE_VERT 6


/******************************************************************************
*                        Function print_welcome()                             *
*******************************************************************************
*                                                                             *
* This function does not take any input. It prints a welcome message for      *
* Alice, asks her if she would like to make the first move, reads the input   *
* from stdin, and clears the input buffer (in case Alice enters more than one *
* character). The function returns 2 if the (first) character typed by Alice  *
* is either n or N.  In all other cases, the function returns 1.              *
*                                                                             *
******************************************************************************/
int print_welcome(void);


/******************************************************************************
*                      Function display_board()                               *
*******************************************************************************
*                                                                             *
* This function receives the board array as input, and prints the current     *
* state of the board to stdout. The function expects the value of every cell  *
* in the board array to be either 0, 1, or 2, where 1 denotes stones of the   *
* first player (printed as X) while 2 denotes stones of the second player     *
* (printed as O). A cell whose value is 0 denotes a place on the board that   *
* is not occupied by a stone of either player.                                *
*                                                                             *
* The format of the print-out follows the example below. The width of every   *
* cell is three characters, and the stone occupying this cell (if any) is the *
* middle character. Vertical lines separate between cells in the same row,    *
* while the rows themselves are separated by a line of hyphens along with '+' *
* characters. Right under the board, the function prints the indices of the   *
* columns, with each such index centered in its column.                       *
*                                                                             *
* +---+---+---+---+---+---+---+                                               *
* |   |   |   |   |   |   |   |                                               *
* +---+---+---+---+---+---+---+                                               *
* | O |   |   |   |   |   |   |                                               *
* +---+---+---+---+---+---+---+                                               *
* | X |   |   |   |   |   |   |                                               *
* +---+---+---+---+---+---+---+                                               *
* | O |   |   |   |   |   |   |                                               *
* +---+---+---+---+---+---+---+                                               *
* | O |   |   |   |   |   |   |                                               *
* +---+---+---+---+---+---+---+                                               *
* | O | X |   | X |   |   | X |                                               *
* +---+---+---+---+---+---+---+                                               *
*   1   2   3   4   5   6   7                                                 *
*                                                                             *
******************************************************************************/
void display_board(int board[][BOARD_SIZE_VERT]);


/******************************************************************************
*                         Function random_move()                              *
*******************************************************************************
*                                                                             *
* This function receives the board array and player number as input. It then  *
* makes a valid random move. To this end, the function generates uniformly at * 
* random an integer m in the range 1,2, ..., BOARD_SIZE_HORIZ using a call to *
* the rand() standard library function. It then verifies that this integer m  *
* constitutes a valid move, by calling the function is_column_full(). If m is *
* a valid move, the function returns m. If not (that is, if the m-th column   *
* is full, the function repeat the process until a valid move is generated by *
* rand(). Note that the function assumes that at least one cell in the array  *
* board is 0; otherwise it enters into an infinite~loop!                      *
*                                                                             *
* Prior to returning m, the function also updates the state of the board by   *
* making the function call update_board(board,m,computer_num).                *
*                                                                             *
******************************************************************************/
int random_move(int board[][BOARD_SIZE_VERT], int computer_num);


/******************************************************************************
*                      Function player_move()                                 *
*******************************************************************************
*                                                                             *
* This function receives the board array and player number as input. It then  *
* prompts Alice to enter her move, reads her input from stdin, and clears the *
* input buffer. If Alice enters anything other than an integer in the range   *
* 1,2, ..., BOARD_SIZE_HORIZ, the function prints "Not a valid move. Enter a  *
* column number!" to stdout and prompts Alice again to enter a move. If Alice *
* enters an integer m in the appropriate range, the function verifies that    *
* the corresponding column is not full by calling is_column_full(). If the    *
* column is full, the function prints "This column is full. Try again!" and   *
* again prompt Alice to enter a move. When Alice does (eventually) enter      *
* a valid move m, the function updates the state of the board using the call  *
* update_board(board,m,player_num) and then returns m.                        *
*                                                                             *
******************************************************************************/
int player_move(int board[][BOARD_SIZE_VERT], int player_num);


/******************************************************************************
*                      Function check_win_or_tie()                            *
*******************************************************************************
*                                                                             *
* This function receives as input the board array and an integer last_move,   *
* which is interpreted as the index of the column where the most recent stone *
* was played. The function calls check_winner() to determine whether the game *
* has been won by either player. If so, the function prints either "Player X  *
* won!" or "Player O won!" and returns true. If there is no winner, the func- *
* tion checks whether the game is drawn: no spaces left on the board. If so,  *
* the function prints "Tie game!" and returns true. Otherwise, the function   *
* returns false, indicating that the game is not yet over.                    *
*                                                                             *
******************************************************************************/
bool check_win_or_tie(int board[][BOARD_SIZE_VERT], int last_move);


/******************************************************************************
*                    Function is_column_full()                                *
*******************************************************************************
*                                                                             *
* This function receives as input the board array and an integer m, which is  *
* is expected to be in the range 1,2, ..., BOARD_SIZE_HORIZ.  The function    *
* returns true if the m-th column of the board is full, and false otherwise.  *
*                                                                             *
******************************************************************************/
bool is_column_full(int board[][BOARD_SIZE_VERT], int m);


/******************************************************************************
*                      Function update_board()                                *
*******************************************************************************
*                                                                             *
* This function receives as input the board array, an integer m which is      *
* expected to be in the range 1,2, ..., BOARD_SIZE_HORIZ, and an integer      *
* player_num which should be either 1 or 2. It then updates the board by      *
* changing the appropriate entry in the m-th column from 0 to player_num.     *
* Note that "the m-th column of the board" actually refers to board[m-1][].   *
*                                                                             *
* The function determine which row in the m-th column to update using the     *
* rule that a stone dropped into a given column always slides down to the     *
* lowest unoccupied row in that column.                                       *
*                                                                             *
******************************************************************************/
void update_board(int board[][BOARD_SIZE_VERT], int m, int player_num);


/******************************************************************************
*                      Function check_winner()                                *
*******************************************************************************
*                                                                             *
* This function receives as input the board array and an integer last_move,   *
* which is interpreted as the index of the column where the most recent stone *
* was played. The function checks whether the placement of this most recent   *
* stone results in a win (4 stones on the board forming a consecutive horiz-  *
* ontal, vertical, or diagonal sequence). If so, the function returns the     *
* player number (either 1 or 2) of the winning player. If there is no winner, *
* the function returns 0.                                                     *
*                                                                             *
******************************************************************************/
int check_winner(int board[][BOARD_SIZE_VERT], int last_move);


/******************************************************************************
*                        Function best_move()                                 *
*******************************************************************************
*                                                                             *
* This EXTRA-CREDIT function receives the board array and a player number as  *
* input. It returns the best (valid) move m for the given board position.     *
*                                                                             *
* Prior to returning m, the function also updates the state of the board by   *
* making the function call update_board(board,m,computer_num).                *
*                                                                             *
******************************************************************************/
int best_move(int board[][BOARD_SIZE_VERT], int computer_num);


#endif

Program connect4best.c (this was provided, and we weren't allowed to touch it. This version is actually slightly edited, because the original called random_move() instead of best_move() for the computer's turn, and best_move() is what I'm working on here)

#include "connect4_functions.h"

/*@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
 This program plays a game of Connect-4 between the user (herein called Alice)
 and the computer. The declarations of all the functions used in this program
 are in connect4_functions.h. The definitions of these functions are expected
 in c4_functions.c.
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@*/

int main()
{
   int board[BOARD_SIZE_HORIZ][BOARD_SIZE_VERT] = { {0} };
   int player_num, computer_num;
   int last_move; 


   /* Ask Alice if she wants to go first */
   player_num = print_welcome();
   if (player_num == 1) computer_num = 2;
   else computer_num = 1;

   /* If Alice wants to go first, let her make a move */
   if (player_num == 1)
   {
      display_board(board);
      last_move = player_move(board,player_num);
      display_board(board);
   }


   /* The main loop */

   while (1)
   {
      /* Make a computer move, then display the board */
      last_move = best_move(board,computer_num);
      printf("Computer moved in column: %d\n", last_move);
      display_board(board);

      /* Check whether the computer has won */
      if (check_win_or_tie(board,last_move)) return 0;


      /* Let Alice make a move, then display the board */
      last_move = player_move(board,player_num);
      display_board(board);

      /* Check whether Alice has won */
      if (check_win_or_tie(board,last_move)) return 0;


   } /* end of while (1) */

} /* end of main() */

/******************************************************************************
                                End of file
******************************************************************************/
\$\endgroup\$
1
  • 2
    \$\begingroup\$ Welcome to Code Review! The code to be reviewed must be embedded in the post itself; external links are to be treated as supplemental background information only. Don't worry about length: the limit is 64 kiB. \$\endgroup\$ Dec 20, 2017 at 15:25

1 Answer 1

2
\$\begingroup\$

I had a look at your functions file. You wrote code that spelled out all the checks. That may have flavored your thinking for the AI as well.

Use special purpose tools

You are sharing code from Google Drive. There are special purpose sites for sharing code, like https://bitbucket.org/ or https://github.com/. If you use them, other people can easily see your code, download it, even make changes and send them back to you in a standardized way.

Abstract your concepts

Instead of, for example, checking if a position is filled, then if a position+1 is filled, then if a position+2 is filled, then if a position+3 is filled, try creating some slightly more abstract concepts and implementing them, so you can use loops rather than copy/paste.

For example, create a Direction enum. Use that to index some offset values. Then, instead of writing functions for each possible direction, just loop over 4 iterations of adding the offsets:

enum { EMPTY, PLAYER1, PLAYER2, NUM_CELL_VALUES };

struct vector { int dr, dc; };
enum { UP, UP_RIGHT, RIGHT, DOWN_RIGHT, DOWN, DOWN_LEFT, LEFT, UP_LEFT, NUM_DIRECTIONS };

struct vector Direction_vectors[NUM_DIRECTIONS] = {
    { .dr=-1, .dc=0 }, // UP
    { .dr=-1, .dc=1 }, // UP_RIGHT
    { .dr=0,  .dc=1 }, // RIGHT
    // ...
};


this_player = one of PLAYER1 or PLAYER2

dir = UP_RIGHT;
vector = Direction_vectors[dir];

// NOTE: Not checking for board edges here.

int counts[NUM_CELL_VALUES] = { 0 };

for (offset = 0; offset < 4; ++offset) {
    value = board[row + offset * vector.dr][col + offset * vector.dc];
    counts[value]++;
}

if (counts[PLAYER1] && counts[PLAYER2]) {
    score = -1; // Mixed values, no good.
} else {
    score = counts[this_player];
}

// Now score is -1, or how many cells in a line.

By creating a mechanism for dealing with abstract direction vectors, the code above can be used to compute a score in any direction. So you would only need one function - "what's the score from this point in this direction" instead of your many functions (score up/down, score diagonally, score horizontally, etc.).

Block the other player

Your "AI" function seems to focus on trying to make a simple pattern, without focusing on what the other player will do. You can probably improve your score by evaluating what the other player is likely to do, and biasing your own moves to counter that.

For example, suppose you determine that you have two moves of equal benefit to you: there are two cells adjacent and you can append a third cell to either end of the line.

If one of those cells is "important" to the other player, and the other is not, then you should prioritize blocking the important cell before the unimportant cell. Both of them help you equally, but one hinders the other player at the same time.

To this end, I encourage you to design a "scoring" system for cells. Each cell can be rated for its value to a particular player - the cell has two scores! You will want to pick the cell that scores highest for you while also scoring highest for the opponent.

There are various ways to do this. One obvious approach might be to compute the number of winning configurations that a cell could participate in. A cell in a corner would have fewer than a cell in the middle of the board, and this number would go down when opposing cells were positioned. However, it wouldn't go up when own-side cells were positioned, so an additional metric would be needed...

\$\endgroup\$
2
  • \$\begingroup\$ Thank you! This is very useful. I'll have to go over enums and structures to use this (wasn't taught). Just to check my understanding, the code you suggested only checks the up-right score, correct? So I would need a different one for each of the types of wins, and adjust the score accordingly for each? And in general terms, this would be checking every cell in the board, or just the empty ones (or full ones)? I guess I really just need to read up on these types and functions :) \$\endgroup\$
    – W. MacTurk
    Dec 21, 2017 at 15:23
  • 1
    \$\begingroup\$ You would need to loop over that code, or call a function with that code in it, to check other directions. The point of that code is to show how you can make the direction abstract, so instead of a separate check-up-right function, you just need a check-in-this-direction(dir) function. \$\endgroup\$
    – aghast
    Dec 21, 2017 at 19:20

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.