首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >Tac趾在C/ ncurses修订中的应用

Tac趾在C/ ncurses修订中的应用
EN

Code Review用户
提问于 2018-12-30 01:34:33
回答 5查看 876关注 0票数 8

在这篇文章中有人说我可以要求对我的修订版进行审查:具有简单AI的Tac Tac脚趾的

我用答案中的建议从头写了一遍。我特别做了以下工作:

  1. 我去掉了全局变量。对于为数不多的全局常量,我使用#defines代替。
  2. 按照建议,我使用数组而不是编号变量的列表。
  3. 虽然没有人建议我这么做,但我确实改进了人工智能。我认为这就是它不短的主要原因。

有一套特别的功能,我想我可以在AI逻辑阶段把它组合成一个带有开关的函数。我在代码中对它们进行了注释,这是我想要得到的关于如何做的一些反馈。

我要感谢上一篇文章中的每一个人的建议和建议。有一些我没有听清楚,而且出于不同的原因:

  1. 我并没有停止使用rand(),因为就我的目的而言,它似乎足够随意。我自己做了一个测试,使用了10,000次尝试,并检查了它在0到99之间选择每个数字的频率,这是足够随机的。所以在这个项目中我一直在使用它。对于那些仍然建议我使用另一种随机函数的人,我愿意接受关于如何最好地做到这一点的建议。我不介意做我自己的随机函数,我会被提示做这件事。尽管如此,很难想象即使在兰德()上我也能如何改进。
  2. 我想用Structs来模仿面向对象的瓷砖空间,但我无法完全理解如何做到这一点。在Python中,这类事情是微不足道的,但在C语言中,我不太容易理解。关于如何将Structs集成到其中以获得某种OOP的技巧将是非常受欢迎的。

AI上的注意事项:我这次让事情变得更困难了。它大部分时间都会打败人类玩家。如果我没有故意建立一个“放屁”功能,使它在一定比例的时间内失败,那么它将在每一次(不打领带)中击败一个人。

请注意代码本身:任何想要将其用于自己目的的人都是非常受欢迎的。虽然我对此感到非常自豪,但我认识到这是业余的东西。如果有人想玩一些抽搐脚趾,或能想到一个用途,他们自己,他们是非常欢迎的。如果您有ncurses,那么在大多数linux系统上它应该很容易编译。

关于评论的注意:我的评论风格引起了其他线程中的一些评审员的关注,所以我对它做了一些修改。我担心这可能太冗长了,但我希望对于那些不太熟悉诅咒的人来说,这是一件容易的事情。

注意长度:这才是真正的缺点。虽然我实现了来自另一个线程的许多建议,但我的代码实际上更长.而不是更短。至少有一组函数我可能可以组合,但我如何缩短它呢?

与以往一样,欢迎任何和所有意见和建议。在使用这种语言进行更复杂的项目之前,我想用这样的简单项目来确定一种坚实的编码风格。谢谢!下面是代码,不需要再多说了:

代码语言:javascript
复制
// tic tac toe v2 using suggestions from Stack Exchange for better style
// Minus the struct stuff which I don't quite understand just yet.

// ncurses for, well, ncurses
#include <ncurses.h>
// time for the random seed
#include <time.h>
// string.h for strlen() 
#include <string.h>
// stdlib and stdio because why not
#include <stdlib.h>
#include <stdio.h>
// ctype.h for toupper()
#include <ctype.h>

// #define's for the COLOR_PAIRs
#define X_COLOR 1
#define O_COLOR 2
#define BG_COLOR 3

// #defines used as a global constant
#define num_spaces 9

// Function Declarations
void init_spaces(char *space_ptr);
void paint_board(char playable_spaces[num_spaces]);
void take_turn(char side, char *space_ptr, char playable_spaces[num_spaces]);
void victory_splash(int game_over_state);
void paint_background();
void player_turn(char *space_ptr, char playable_spaces[num_spaces], char side);
void ai_turn(char *space_ptr, char playable_spaces[num_spaces], char side);
void set_color_ai_side(char ai_side);
void set_color_side(char side);
int main_menu();
int evaluate_board(char playable_spaces[num_spaces]);
int spaces_left(char playable_spaces[num_spaces]);
int ai_fart(const int chance_to_fart);
int pick_random_space(char playable_spaces[num_spaces]);
int check_for_winning_move(char playable_spaces[num_spaces], char ai_side);
int check_for_block(char playable_spaces[num_spaces], char side);
int check_for_2_space_path(char playable_spaces[num_spaces], char ai_side);
char pick_side();

int main(){
    // To-Do: Try the time(NULL) method for srand initialization and see if it works the same
    time_t t;
    srand((unsigned) time(&t));
    char playable_spaces[num_spaces] = {'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X'};
    char *space_ptr = &playable_spaces[0];

    // Game over splash
    char game_over_str[] =  " Game Over! Any key to continue... ";
    char go_padding[] = "                                   ";
    int game_over_len = strlen(game_over_str);
    int row, col, x, y;

    //curses init
    initscr();
    cbreak();
    keypad(stdscr, 1);
    curs_set(0);
    start_color();
    init_pair(X_COLOR, COLOR_CYAN, COLOR_BLACK);
    init_pair(O_COLOR, COLOR_GREEN, COLOR_BLACK);
    init_pair(BG_COLOR, COLOR_YELLOW, COLOR_BLACK);
    noecho();

    // Main Menu outer loop
    int running = 1;
    while(running){
        curs_set(0);
        // Main menu function quits or continues
        running = main_menu();
        // In-Game inner loop
        if(running == 0){
            break;
        }
        int playing = 1;
        while(playing){
            // Init all spaces to blank
            init_spaces(space_ptr);
            // Player picks their side.
            char side = pick_side();
            // The inner, inner turn loop
            int turning = 1;
            while(turning){
                int game_over = 0;
                // Paint the board state as it is that turn
                paint_board(playable_spaces);
                // Function that governs the turn cycle
                take_turn(side, space_ptr, playable_spaces);
                // Evaluate the board for game over state
                game_over = evaluate_board(playable_spaces);
                if(game_over > 0){
                    // paint the board with a splash on game over
                    // so the player can evaluate the board for a moment
                    paint_board(playable_spaces);
                    getmaxyx(stdscr, row, col);
                    y = row / 2 + 6;
                    x = col / 2 - game_over_len / 2;
                    attron(COLOR_PAIR(BG_COLOR));
                    mvprintw(y++, x, go_padding);
                    mvprintw(y++, x, game_over_str);
                    mvprintw(y, x, go_padding);
                    refresh();
                    getch();
                    // call victory_splash with int game_over as a parameter
                    // 1 = X wins, 2 = O wins, 3 = Tie
                    victory_splash(game_over);
                    // Reset the turning and playing loops to effectively start over
                    turning = 0;
                    playing = 0;
                }
            }
        }
    }

    // end curses
    endwin();

    return 0;
}


void init_spaces(char *space_ptr){ 
    // init all the spaces to ' ';
    int i;
    for(i = 0; i < 9; i++){
        *space_ptr = ' ';
        space_ptr++;
    }
}

void paint_board(char playable_spaces[num_spaces]){
    // paint the board and the playable spaces
    clear();
    paint_background();
    char break_lines[] = " ------- ";
    char play_lines[] =  " | | | | ";
    char padding[] =     "         ";
    int row, col, x, y;
    getmaxyx(stdscr, row, col);
    y = row / 2 - 4;
    int len;
    len = strlen(padding);
    x = col / 2 - len / 2;
    int k;
    const int num_lines = 9;
    attron(COLOR_PAIR(BG_COLOR));
    for(k = 0; k < num_lines; k++){
        // Paint the board itself without the pieces
        if(k == 0 || k == num_lines - 1){
            mvprintw(y + k, x, padding);
        }else{
            if(k % 2 == 0){
                mvprintw(y + k, x, play_lines);
            }else{
                mvprintw(y + k, x, break_lines);
            }
        }
    }
    attroff(COLOR_PAIR(BG_COLOR));
    // insert Xs and Os:
    // First set the dynamic x and y coordinates based on terminal size
    int playable_x[num_spaces] = {x+2, x+4, x+6, x+2, x+4, x+6, x+2, x+4, x+6};
    int playable_y[num_spaces] = {y+2, y+2, y+2, y+4, y+4, y+4, y+6, y+6, y+6};
    for(k = 0; k < num_spaces; k++){
        // For each of the playable spaces, first set the color
        if(playable_spaces[k] == 'O'){
            attron(COLOR_PAIR(O_COLOR));        
        }else if(playable_spaces[k] == 'X'){
            attron(COLOR_PAIR(X_COLOR));
        }else{
            attron(COLOR_PAIR(BG_COLOR));
        }
        // then insert the char for that space into the proper spot on the terminal
        mvaddch(playable_y[k], playable_x[k], playable_spaces[k]);
    }
    // refresh the screen
    refresh();
}

void take_turn(char side, char *space_ptr, char playable_spaces[num_spaces]){
    // using "side" to determine the order, call the functions to play a whole turn
    if(side == 'X'){
        player_turn(space_ptr, playable_spaces, side);
        paint_board(playable_spaces);
        if(spaces_left(playable_spaces)){
            if(!(evaluate_board(playable_spaces))){
                ai_turn(space_ptr, playable_spaces, side);
                paint_board(playable_spaces);
            }
        }
    }else if(side == 'O'){
        ai_turn(space_ptr, playable_spaces, side);
        paint_board(playable_spaces);
        if(spaces_left(playable_spaces)){
            if(!(evaluate_board(playable_spaces))){
                player_turn(space_ptr, playable_spaces, side);
                paint_board(playable_spaces);
            }
        }
    }
}

int main_menu(){
    clear();
    // Takes user input and returns an int that quits or starts a game
    int row, col, x, y;
    char error_string[] = " Invalid Input! Any key to try again... ";
    int error_str_len = strlen(error_string);
    char str1[] =      " NCURSES TIC TAC TOE (v2) ";
    char padding[] =   "                          ";
    char str2[] =      "    (P)lay or (Q)uit?     ";
    int len = strlen(str1);
    paint_background();
    getmaxyx(stdscr, row, col);
    y = row / 2 - 2;
    x = col / 2 - len / 2;
    mvprintw(y++, x, padding);
    mvprintw(y++, x, str1);
    mvprintw(y++, x, padding);
    mvprintw(y++, x, str2);
    mvprintw(y++, x, padding);
    int input;
    refresh();
    // get user input and return it
    input = toupper(getch());
    if(input == 'P'){
        return 1;
    }else if(input == 'Q'){
        return 0;
    }else{
        // call the function again if the input is bad
        x = col / 2 - error_str_len / 2;
        mvprintw(++y, x, error_string);
        getch();
        main_menu();
    }
}

int evaluate_board(char playable_spaces[num_spaces]){
    // Evaluates the state of the playable spaces and either does nothing
    // or ends the game.
    // Check all the possible winning combinations:
    if(playable_spaces[0] == 'X' && playable_spaces[1] == 'X' && playable_spaces[2] == 'X'){
        return 1;
    }else if(playable_spaces[3] == 'X' && playable_spaces[4] == 'X' && playable_spaces[5] == 'X'){
        return 1;
    }else if(playable_spaces[6] == 'X' && playable_spaces[7] == 'X' && playable_spaces[8] == 'X'){
        return 1;
    }else if(playable_spaces[0] == 'X' && playable_spaces[3] == 'X' && playable_spaces[6] == 'X'){
        return 1;
    }else if(playable_spaces[1] == 'X' && playable_spaces[4] == 'X' && playable_spaces[7] == 'X'){
        return 1;
    }else if(playable_spaces[2] == 'X' && playable_spaces[5] == 'X' && playable_spaces[8] == 'X'){
        return 1;
    }else if(playable_spaces[0] == 'X' && playable_spaces[4] == 'X' && playable_spaces[8] == 'X'){
        return 1;
    }else if(playable_spaces[2] == 'X' && playable_spaces[4] == 'X' && playable_spaces[6] == 'X'){
        return 1;
    }else if(playable_spaces[0] == 'O' && playable_spaces[1] == 'O' && playable_spaces[2] == 'O'){
        return 2;
    }else if(playable_spaces[3] == 'O' && playable_spaces[4] == 'O' && playable_spaces[5] == 'O'){
        return 2;
    }else if(playable_spaces[6] == 'O' && playable_spaces[7] == 'O' && playable_spaces[8] == 'O'){
        return 2;
    }else if(playable_spaces[0] == 'O' && playable_spaces[3] == 'O' && playable_spaces[6] == 'O'){
        return 2;
    }else if(playable_spaces[1] == 'O' && playable_spaces[4] == 'O' && playable_spaces[7] == 'O'){
        return 2;
    }else if(playable_spaces[2] == 'O' && playable_spaces[5] == 'O' && playable_spaces[8] == 'O'){
        return 2;
    }else if(playable_spaces[0] == 'O' && playable_spaces[4] == 'O' && playable_spaces[8] == 'O'){
        return 2;
    }else if(playable_spaces[2] == 'O' && playable_spaces[4] == 'O' && playable_spaces[6] == 'O'){
        return 2;
    }else{
        // Check all spaces for a tie
        int hits = 0;
        int i;
        for(i = 0; i < num_spaces; i++){
            if(playable_spaces[i] != ' '){
                hits++;
            }
        }
        if(hits >= num_spaces){
            return 3;
        }else{
            return 0;
        }
    }
}

char pick_side(){
    // Takes user input and returns the chosen side
    clear();
    paint_background();
    int row, col, x, y;
    char str1[] =    " Press 'X' for X, 'O' for O, or 'R' for random! ";
    char str2[] =    "        Good choice! Any key to continue...     ";
    char padding[] = "                                                ";
    char err_str[] = "      Invalid input! Any key to continue...     ";
    int len = strlen(str1);
    getmaxyx(stdscr, row, col);
    y = row / 2 - 2;
    x = col / 2 - len / 2;
    mvprintw(y++, x, padding);
    mvprintw(y++, x, str1);
    mvprintw(y++, x, padding);
    int input;
    int pick;
    refresh();
    // Get user input for picking a side. 'R' is random.
    input = toupper(getch());
    if(input == 'X' || input == 'O'){
        mvprintw(y, x, str2);
        refresh();
        getch();
        return (char) input;
    }else if(input == 'R'){
        pick = rand() % 2;
        if(pick == 0){
            input = 'X';
        }else if(pick == 1){
            input = 'O';
        }
        mvprintw(y, x, str2);
        refresh();
        getch();
        return (char) input;
    }else{
        // Call the function again on bad input
        mvprintw(y, x, err_str);
        refresh();
        getch();
        pick_side();
    }
}

void victory_splash(int game_over_state){
    // Takes the game over state and creates a victory splash
    char padding[] = "                                   ";
    char *str1 =     "              X Wins!              ";
    char *str2 =     "              O Wins!              ";
    char str3[] =    "         any key to continue...    ";
    char *str4 =     "             A tie game!           ";
    int len = strlen(padding);
    char *vic_pointer = NULL;
    // To avoid code duplication, use a pointer to pick the right string
    if(game_over_state == 1){
        vic_pointer = str1;
    }else if(game_over_state == 2){
        vic_pointer = str2;
    }else if(game_over_state == 3){
        vic_pointer = str4;
    }
    clear();
    paint_background();
    int row, col, x, y;
    getmaxyx(stdscr, row, col);
    y = row / 2 - 2;
    x = col / 2 - len / 2;
    mvprintw(y++, x, padding);
    mvprintw(y++, x, vic_pointer);
    mvprintw(y++, x, padding);
    mvprintw(y, x, str3);
    refresh();
    getch();
}

void paint_background(){
    // Paints an elaborate flashy background
    int row, col, x, y;
    int pick;
    getmaxyx(stdscr, row, col);
    for(y = 0; y <= row; y++){
        for(x = 0; x <= col; x++){
            pick = rand() % 3;
            if(pick == 0){
                attron(COLOR_PAIR(X_COLOR));
                mvprintw(y, x, "X");
                attroff(COLOR_PAIR(X_COLOR));
            }else if(pick == 1){
                attron(COLOR_PAIR(O_COLOR));
                mvprintw(y, x, "O");
                attroff(COLOR_PAIR(O_COLOR));
            }else if(pick == 2){
                attron(COLOR_PAIR(BG_COLOR));
                mvprintw(y, x, " ");
                attroff(COLOR_PAIR(BG_COLOR));
            }
        }
    }
    refresh();
}

void player_turn(char *space_ptr, char playable_spaces[num_spaces], char side){
    // Function for the player turn
    char padding[] =  "                                                ";
    char str1[] =     "    Use arrow keys to move and 'P' to place!    ";
    char str2[] =     "                   Good move!                   ";
    char str3[] =     "                 Invalid input!                 ";
    char str4[] =     "             You can't move that way!           ";
    char str5[] =     "              Space already occupied!           ";
    int len = strlen(padding);
    int row, col, x, y;
    getmaxyx(stdscr, row, col);
    const int board_line_len = 9;
    const int board_lines = 9;
    y = row / 2 - board_line_len / 2;
    x = col / 2 - board_line_len / 2;
    // Use the same method of dynamically measuring where the spaces are at using
    // terminal size as in the paint_board() function.
    int playable_x[num_spaces] = {x+2, x+4, x+6, x+2, x+4, x+6, x+2, x+4, x+6};
    int playable_y[num_spaces] = {y+2, y+2, y+2, y+4, y+4, y+4, y+6, y+6, y+6};
    // The variables and mvprintw functions for the "info line"
    const int info_line_y = (row / 2 - board_lines / 2) + 10;
    const int info_line_x = col / 2 - len / 2;
    mvprintw(info_line_y - 1, info_line_x, padding);
    mvprintw(info_line_y, info_line_x, str1);
    mvprintw(info_line_y + 1, info_line_x, padding);
    // Using a loop and pointers to collect user input
    int moving = 1;
    int input;
    int *pos_x = &playable_x[0];
    int *pos_y = &playable_y[0];
    move(*pos_y, *pos_x);
    curs_set(1);
    refresh();
    while(moving){
        // For each movement key, if the move is valid, use pointer
        // arithmetic to mov pos_x and pos_y around.
        input = toupper(getch());
        if(input == KEY_UP){
            if(*pos_y != playable_y[0]){
                pos_y -= 3;
                move(*pos_y, *pos_x);
                refresh();
            }else{
                mvprintw(info_line_y, info_line_x, str4);
                move(*pos_y, *pos_x);
                refresh();
            }
        }else if(input == KEY_DOWN){
            if(*pos_y != playable_y[6]){
                pos_y += 3;
                move(*pos_y, *pos_x);
                refresh();
            }else{
                mvprintw(info_line_y, info_line_x, str4);
                move(*pos_y, *pos_x);
                refresh();
            }
        }else if(input == KEY_LEFT){
            if(*pos_x != playable_x[0]){
                pos_x -= 1;
                move(*pos_y, *pos_x);
                refresh();
            }else{
                mvprintw(info_line_y, info_line_x, str4);
                move(*pos_y, *pos_x);
                refresh();
            }
        }else if(input == KEY_RIGHT){
            if(*pos_x != playable_x[2]){
                pos_x += 1;
                move(*pos_y, *pos_x);
                refresh();
            }else{
                mvprintw(info_line_y, info_line_x, str4);
                move(*pos_y, *pos_x);
                refresh();
            }
        }else if(input == 'P'){
            // I wanted to use KEY_ENTER instead of 'P' but it would not work
            // for some reason. When the user presses 'P' it checks where the
            // cursor is and sets the space_ptr to the appropriate index in the
            // playable_spaces array.
            if(*pos_y == playable_y[0] && *pos_x == playable_x[0]){
                space_ptr = &playable_spaces[0];                
            }else if(*pos_y == playable_y[1] && *pos_x == playable_x[1]){
                space_ptr = &playable_spaces[1];
            }else if(*pos_y == playable_y[2] && *pos_x == playable_x[2]){
                space_ptr = &playable_spaces[2];
            }else if(*pos_y == playable_y[3] && *pos_x == playable_x[3]){
                space_ptr = &playable_spaces[3];
            }else if(*pos_y == playable_y[4] && *pos_x == playable_x[4]){
                space_ptr = &playable_spaces[4];
            }else if(*pos_y == playable_y[5] && *pos_x == playable_x[5]){
                space_ptr = &playable_spaces[5];
            }else if(*pos_y == playable_y[6] && *pos_x == playable_x[6]){
                space_ptr = &playable_spaces[6];
            }else if(*pos_y == playable_y[7] && *pos_x == playable_x[7]){
                space_ptr = &playable_spaces[7];
            }else if(*pos_y == playable_y[8] && *pos_x == playable_x[8]){
                space_ptr = &playable_spaces[8];
            }
            // Then checks to see if that space is empty.
            // If so it sets the color properly and then places the piece.
            if(*space_ptr == ' '){
                if(side == 'X'){
                    attron(COLOR_PAIR(X_COLOR));
                    mvaddch(*pos_y, *pos_x, 'X');
                    attron(COLOR_PAIR(BG_COLOR));
                    *space_ptr = 'X';
                }else if(side == 'O'){
                    attron(COLOR_PAIR(O_COLOR));
                    mvaddch(*pos_y, *pos_x, 'O');
                    attron(COLOR_PAIR(BG_COLOR));
                    *space_ptr = 'O';
                }
                refresh();
                moving = 0;
            }else{
                mvprintw(info_line_y, info_line_x, str5);
                move(*pos_y, *pos_x);
                refresh();
            }
        }else{
            mvprintw(info_line_y, info_line_x, str3);
            move(*pos_y, *pos_x);
            refresh();
        }
    }
}

//////////////////////////////////////////////////////////////////////////////////////
// Begin AI Logic ////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////

void ai_turn(char *space_ptr, char playable_spaces[num_spaces], char side){
    // wrapper for the AI turn
    /*
        Note: Since it is easy to accidentally create an unbeatable AI for tic tac toe
              I am building into the AI the chance for it to not make the optimal move.
                    This intentional fuzziness will be built into the functions that check for
                    avaialable spaces. When they find an optimal move they may just decide
                    to return 0 anyway.

        P-Code:
        if center square not taken, take center square 70% of the time;
        else:
            if opponent about to win, block them 90% of the time;
            elif self about to win take winning spot 90% of the time;
            else pick a random open spot;
    */
    // The chances for the AI to blow a move
    const int chance_to_fart_big_move = 10;
    const int chance_to_fart_center = 30;
    // Picking the character for the AI to use in its calculations
    char ai_side;
    if(side == 'X'){
        ai_side = 'O';
    }else if(side == 'O'){
        ai_side = 'X';
    }
    // Check the board state with a few functions.
    // These all return 0 if FALSE and the number of a valid
    // index to move into if TRUE
    int can_block_opponent = check_for_block(playable_spaces, side);
    int can_winning_move = check_for_winning_move(playable_spaces, ai_side);
    // Flow through the decision making logic applying the functions and checking for a fart
    int thinking = 1;
    int picked_space;
    while(thinking){
        if(playable_spaces[4] == ' '){
            if(!(ai_fart(chance_to_fart_center))){
                picked_space = 4;
                thinking = 0;
                break;
            }
        }
        if(can_winning_move){
            if(!(ai_fart(chance_to_fart_big_move))){
                picked_space = can_winning_move;
                thinking = 0;
            }else{
                picked_space = pick_random_space(playable_spaces);
                thinking = 0;
            }
        }else if(can_block_opponent){
            if(!(ai_fart(chance_to_fart_big_move))){
                picked_space = can_block_opponent;
                thinking = 0;
            }else{
                picked_space = pick_random_space(playable_spaces);
                thinking = 0;
            }
        }else{
            picked_space = pick_random_space(playable_spaces);
            thinking = 0;
        }
    }
    space_ptr = &playable_spaces[picked_space];
    if(ai_side == 'X'){
        attron(COLOR_PAIR(X_COLOR));
    }else if(ai_side == 'O'){
        attron(COLOR_PAIR(O_COLOR));
    }
    *space_ptr = ai_side;
    attron(COLOR_PAIR(BG_COLOR));
}


int ai_fart(const int chance_to_fart){
    // Takes the fart chance and returns 1 if the AI blows the move, 0 otherwise
    int roll;
    roll = rand() % 100 + 1;
    if(roll < chance_to_fart){
        return 1;
    }else{
        return 0;
    }
}

int pick_random_space(char playable_spaces[num_spaces]){
    // Returns a random open space on the board
    int roll;
    int rolling = 1;
    int pick;
    while(rolling){
        roll = rand() % num_spaces;
        if(playable_spaces[roll] == ' '){
            pick = roll;
            rolling = 0;
        }else{
            continue;
        }
    }
    return pick;
}

int check_for_winning_move(char playable_spaces[num_spaces], char ai_side){
    // Checks to see if the AI can win the game with a final move and returns the
    // index of the valid move if TRUE, returns 0 if FALSE
    int space;
    int pick;
    int picked = 0;
    for(space = 0; space < num_spaces; space++){
        // For each space: Check to see if it is a potential winning space and if so
        // switch "picked" to 1 and set "pick" to the winning index
        switch(space){
            case(0):
                if(playable_spaces[space] == ' '){
                    if(playable_spaces[1] == ai_side && playable_spaces[2] == ai_side){
                        pick = space;
                        picked = 1;
                    }else if(playable_spaces[3] == ai_side && playable_spaces[6] == ai_side){
                        pick = space;
                        picked = 1;
                    }else if(playable_spaces[4] == ai_side && playable_spaces[8] == ai_side){
                        pick = space;
                        picked = 1;
                    }
                }
                break;
            case(1):
                if(playable_spaces[space] == ' '){
                    if(playable_spaces[0] == ai_side && playable_spaces[2] == ai_side){
                        pick = space;
                        picked = 1;
                    }else if(playable_spaces[4] == ai_side && playable_spaces[7] == ai_side){
                        pick = space;
                        picked = 1;
                    }
                }
                break;
            case(2):
                if(playable_spaces[space] == ' '){
                    if(playable_spaces[1] == ai_side && playable_spaces[0] == ai_side){
                        pick = space;
                        picked = 1;
                    }else if(playable_spaces[4] == ai_side && playable_spaces[6] == ai_side){
                        pick = space;
                        picked = 1;
                    }else if(playable_spaces[5] == ai_side && playable_spaces[8] == ai_side){
                        pick = space;
                        picked = 1;
                    }
                }
                break;
            case(3):
                if(playable_spaces[space] == ' '){
                    if(playable_spaces[4] == ai_side && playable_spaces[5] == ai_side){
                        pick = space;
                        picked = 1;
                    }else if(playable_spaces[0] == ai_side && playable_spaces[6] == ai_side){
                        pick = space;
                        picked = 1;
                    }
                }
                break;
            case(4):
                if(playable_spaces[space] == ' '){
                    if(playable_spaces[1] == ai_side && playable_spaces[7] == ai_side){
                        pick = space;
                        picked = 1;
                    }else if(playable_spaces[3] == ai_side && playable_spaces[5] == ai_side){
                        pick = space;
                        picked = 1;
                    }else if(playable_spaces[0] == ai_side && playable_spaces[8] == ai_side){
                        pick = space;
                        picked = 1;
                    }else if(playable_spaces[6] == ai_side && playable_spaces[2] == ai_side){
                        pick = space;
                        picked = 1;
                    }
                }
                break;
            case(5):
                if(playable_spaces[space] == ' '){
                    if(playable_spaces[8] == ai_side && playable_spaces[2] == ai_side){
                        pick = space;
                        picked = 1;
                    }else if(playable_spaces[3] == ai_side && playable_spaces[4] == ai_side){
                        pick = space;
                        picked = 1;
                    }
                }
                break;
            case(6):
                if(playable_spaces[space] == ' '){
                    if(playable_spaces[4] == ai_side && playable_spaces[2] == ai_side){
                        pick = space;
                        picked = 1;
                    }else if(playable_spaces[7] == ai_side && playable_spaces[8] == ai_side){
                        pick = space;
                        picked = 1;
                    }else if(playable_spaces[3] == ai_side && playable_spaces[0] == ai_side){
                        pick = space;
                        picked = 1;
                    }
                }
                break;
            case(7):
                if(playable_spaces[space] == ' '){
                    if(playable_spaces[6] == ai_side && playable_spaces[8] == ai_side){
                        pick = space;
                        picked = 1;
                    }else if(playable_spaces[4] == ai_side && playable_spaces[1] == ai_side){
                        pick = space;
                        picked = 1;
                    }
                }
                break;
            case(8):
                if(playable_spaces[space] == ' '){
                    if(playable_spaces[5] == ai_side && playable_spaces[2] == ai_side){
                        pick = space;
                        picked = 1;
                    }else if(playable_spaces[4] == ai_side && playable_spaces[0] == ai_side){
                        pick = space;
                        picked = 1;
                    }else if(playable_spaces[7] == ai_side && playable_spaces[6] == ai_side){
                        pick = space;
                        picked = 1;
                    }
                }
                break;
        }
    }
    // return winning index if any
    if(picked){
        return pick;
    }else{
        return 0;
    }
}

int check_for_block(char playable_spaces[num_spaces], char side){
    // Checks to see if the AI can block the player from winning the game with a final move
    // and returns the index of the valid move if TRUE, returns 0 if FALSE
    // Note: I am sure there is a way to combine this this function with the
    //  check_for_winning_move() function in order to avoid code duplication, probably using
    //  one more parameter as a switch of some kind. I'd be open to examples of how to do that.
    int space;
    int pick;
    int picked = 0;
    for(space = 0; space < num_spaces; space++){
        // For each space: Check to see if it is a potential winning space and if so
        // switch "picked" to 1 and set "pick" to the winning index
        switch(space){
            case(0):
                if(playable_spaces[space] == ' '){
                    if(playable_spaces[1] == side && playable_spaces[2] == side){
                        pick = space;
                        picked = 1;
                    }else if(playable_spaces[3] == side && playable_spaces[6] == side){
                        pick = space;
                        picked = 1;
                    }else if(playable_spaces[4] == side && playable_spaces[8] == side){
                        pick = space;
                        picked = 1;
                    }
                }
                break;
            case(1):
                if(playable_spaces[space] == ' '){
                    if(playable_spaces[0] == side && playable_spaces[2] == side){
                        pick = space;
                        picked = 1;
                    }else if(playable_spaces[4] == side && playable_spaces[7] == side){
                        pick = space;
                        picked = 1;
                    }
                }
                break;
            case(2):
                if(playable_spaces[space] == ' '){
                    if(playable_spaces[1] == side && playable_spaces[0] == side){
                        pick = space;
                        picked = 1;
                    }else if(playable_spaces[4] == side && playable_spaces[6] == side){
                        pick = space;
                        picked = 1;
                    }else if(playable_spaces[5] == side && playable_spaces[8] == side){
                        pick = space;
                        picked = 1;
                    }
                }
                break;
            case(3):
                if(playable_spaces[space] == ' '){
                    if(playable_spaces[4] == side && playable_spaces[5] == side){
                        pick = space;
                        picked = 1;
                    }else if(playable_spaces[0] == side && playable_spaces[6] == side){
                        pick = space;
                        picked = 1;
                    }
                }
                break;
            case(4):
                if(playable_spaces[space] == ' '){
                    if(playable_spaces[1] == side && playable_spaces[7] == side){
                        pick = space;
                        picked = 1;
                    }else if(playable_spaces[3] == side && playable_spaces[5] == side){
                        pick = space;
                        picked = 1;
                    }else if(playable_spaces[0] == side && playable_spaces[8] == side){
                        pick = space;
                        picked = 1;
                    }else if(playable_spaces[6] == side && playable_spaces[2] == side){
                        pick = space;
                        picked = 1;
                    }
                }
                break;
            case(5):
                if(playable_spaces[space] == ' '){
                    if(playable_spaces[8] == side && playable_spaces[2] == side){
                        pick = space;
                        picked = 1;
                    }else if(playable_spaces[3] == side && playable_spaces[4] == side){
                        pick = space;
                        picked = 1;
                    }
                }
                break;
            case(6):
                if(playable_spaces[space] == ' '){
                    if(playable_spaces[4] == side && playable_spaces[2] == side){
                        pick = space;
                        picked = 1;
                    }else if(playable_spaces[7] == side && playable_spaces[8] == side){
                        pick = space;
                        picked = 1;
                    }else if(playable_spaces[3] == side && playable_spaces[0] == side){
                        pick = space;
                        picked = 1;
                    }
                }
                break;
            case(7):
                if(playable_spaces[space] == ' '){
                    if(playable_spaces[6] == side && playable_spaces[8] == side){
                        pick = space;
                        picked = 1;
                    }else if(playable_spaces[4] == side && playable_spaces[1] == side){
                        pick = space;
                        picked = 1;
                    }
                }
                break;
            case(8):
                if(playable_spaces[space] == ' '){
                    if(playable_spaces[5] == side && playable_spaces[2] == side){
                        pick = space;
                        picked = 1;
                    }else if(playable_spaces[4] == side && playable_spaces[0] == side){
                        pick = space;
                        picked = 1;
                    }else if(playable_spaces[7] == side && playable_spaces[6] == side){
                        pick = space;
                        picked = 1;
                    }
                }
                break;
        }
    }
    // return winning index if any
    if(picked){
        return pick;
    }else{
        return 0;
    }
}

///////////////////////////////////////////////////////////////////////////////////
// End AI Logic ///////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////

int spaces_left(char playable_spaces[num_spaces]){
    // Returns 0 if no spaces left
    int hits = 0;
    int k;
    for(k = 0; k < num_spaces; k++){
        if(playable_spaces[k] == ' '){
        hits++;
        }
    }
    return hits;
}

编辑:谢谢各位!这么多好建议。我们越来越接近一个好的风格!我肯定会再写一遍的。我的目标是开发一个风格与这个项目,我可以继承到更复杂的那些和我收到的批评是无价的。不要因为我做了答案就停止了它的到来!我已经开始使用这些建议中的许多Tic v3。

如果我可以要求一些具体的东西,我会要求有人详细了解我如何使用STRUCTS来模拟我的瓷砖空间的OOP。我想要使用对象来表示瓷砖,这是我想要的更大的项目(例如,类似罗格利埃的项目)。在C语言中,如何使用Structs来实现这一点呢?

编辑2:一个给出答案的人让我把这个放在GitHub上,所以给你:https://github.com/JanitorsBucket/tic_tac_足趾_NCURSES_v2

EN

回答 5

Code Review用户

回答已采纳

发布于 2018-12-30 04:06:45

将定义的

大写

代码语言:javascript
复制
#define num_spaces 9

应该是NUM_SPACES而不是num_spaces

标识本地函数

当您不打算将函数导出到不同的翻译单元时,请告诉编译器。标记除static以外的所有函数main

Const积分args不需要

这是:

代码语言:javascript
复制
int ai_fart(const int chance_to_fart);

不需要它的论点使constconst最常应用于指针类型的参数,以指示值不会被更改,但是对于整型,调用者的副本不能被更改,因此这样做没有什么价值。

使用真正的布尔

有几个变量,包括runningturning,它们是int,但应该是布尔变量。为此目的包括stdbool.h

将枚举用于整型信令值

在至少一个点(evaluate_board的返回值)中,您将返回一个具有特殊意义的整数。这是将返回类型从int更改为typedefenum类型的最佳情况,并让调用方和被调用方都使用枚举的命名常量来使代码更清晰、更有意义,并且(在一定程度上)编译器更能够静态地检查代码。

不要将声明与初始化

分开

你可以在几个地方这样做:

代码语言:javascript
复制
int i;
for(i = 0; i < 9; i++){
    *space_ptr = ' ';
    space_ptr++;
}
// ...
int len;
len = strlen(padding);
// ...
int input;
input = toupper(getch());

不要这样做。只需使用语法int input = toupper(getch());

删除冗余的else

无论何时在return中使用if,下面的代码都不需要else。因此,从这样的代码中删除else

代码语言:javascript
复制
if(input == 'Q'){
    return 0;
}else{
// ...

这在您的代码中多次发生。

使用更多循环

计算机善于重复。这是:

代码语言:javascript
复制
int playable_x[num_spaces] = {x+2, x+4, x+6, x+2, x+4, x+6, x+2, x+4, x+6};

不应在文字中初始化。在循环中初始化它。编译器将就是否应该展开循环做出(通常是正确的)决定。

类似地,在您的evaluate_board函数中,您有高度重复的代码,应该将这些代码重构为循环来检查playable_spaces

更多需要循环的代码-

代码语言:javascript
复制
        if(*pos_y == playable_y[0] && *pos_x == playable_x[0]){
            space_ptr = &playable_spaces[0];    
        // ...            

选择更好的变量名

特别是对于str1str2等。

,不要重复,

这段代码:

代码语言:javascript
复制
    if(input == KEY_UP){
        if(*pos_y != playable_y[0]){
            pos_y -= 3;
            move(*pos_y, *pos_x);
            refresh();
        }else{
            mvprintw(info_line_y, info_line_x, str4);
            move(*pos_y, *pos_x);
            refresh();
        }

几乎逐字重复了四次。你应该把这个放到一个函数里。cases在“check_for_winning_move”和“check_for_block”中也是如此。

不对应该计算的

进行硬编码

例如,不要在任何地方存储或硬编码"9“。"9“只是游戏网格宽度的平方,所以定义游戏网格宽度(3),然后使一个方便的#define等于游戏网格宽度的平方。

制作符号来解释幻数

例如,您使用0作为“不可见的游标”。这是ncurses的错误,因为它没有给您一个符号来使用,但是您仍然可以在代码中通过执行以下操作来修复这个问题

代码语言:javascript
复制
#define INVISIBLE_CUR 0

将数组作为const传递给适当的

您的许多函数接受数组,但不修改它们:

  • spaces_left
  • pick_random_space
  • check_for_winning_move
  • check_for_block

其中的数组参数应该声明为const

使用标准的main签名

代码语言:javascript
复制
int main(int argc, char **argv)

不要滥用循环

您的几个循环需要死掉,特别是:

代码语言:javascript
复制
while(playing){

只执行一次所以杀了它。

代码语言:javascript
复制
for(i = 0; i < 9; i++){
    *space_ptr = ' ';
    space_ptr++;
}

这根本不应该存在。代之以呼叫memset

代码语言:javascript
复制
while(thinking)

这似乎只是一种黑客攻击goto的方法;循环只执行一次。关闭循环并将其内容移动到单独的函数中,以便您可以使用return进行早期终止。

直接使用布尔表达式

这是:

代码语言:javascript
复制
if(roll < chance_to_fart){
    return 1;
}else{
    return 0;
}

是一种反模式;你可以这样做:

代码语言:javascript
复制
return roll < chance_to_fart;

使用更多交换机

下列各项应全部转换为交换机:

代码语言:javascript
复制
if(playable_spaces[k] == 'O'){

代码语言:javascript
复制
input = toupper(getch());
if(input == 'P'){

代码语言:javascript
复制
input = toupper(getch());
if(input == 'X' || input == 'O'){

代码语言:javascript
复制
if(game_over_state == 1){
    vic_pointer = str1;

代码语言:javascript
复制
        pick = rand() % 3;
        if(pick == 0){

UI bug

如果用户试图从网格中导航,您将显示消息You can't move that way,这很好--但该消息掩盖了提示,即使用户导航到不同的有效单元格,也不会消失。有几种方法可以解决这个问题。最简单的方法是将错误消息输出到与提示符不同的行上,并在用户输入有效输入后立即删除错误消息。

示例重构代码

这一点还远未完成。

代码语言:javascript
复制
// Refer to https://codereview.stackexchange.com/questions/210577/tic-tac-toe-in-c-w-ncurses-revision

// tic tac toe v2 using suggestions from Stack Exchange for better style
// Minus the struct stuff which I don't quite understand just yet.

#include <ctype.h>
#include <ncurses.h>
#include <string.h>
#include <stdbool.h>
#include <stdlib.h>
#include <stdio.h>
#include <time.h>

// #define's for the COLOR_PAIRs
#define X_COLOR 1
#define O_COLOR 2
#define BG_COLOR 3

#define SQSIZE 3
#define NUM_SPACES (SQSIZE*SQSIZE)

#define INVISIBLE_CURSOR 0

// Function Declarations
static void init_spaces(char *space_ptr);
static void paint_board(char playable_spaces[NUM_SPACES]);
static void take_turn(char side, char *space_ptr,
                      char playable_spaces[NUM_SPACES]);
static void victory_splash(int game_over_state);
static void paint_background();
static void player_turn(char *space_ptr, char playable_spaces[NUM_SPACES],
                        char side);
static void ai_turn(char *space_ptr, char playable_spaces[NUM_SPACES],
                    char side);
static int pick_ai_space(const char playable_spaces[NUM_SPACES],
                         int chance_to_fart_big_move,
                         int chance_to_fart_center,
                         char side, char ai_side);
static bool main_menu();
static int evaluate_board(char playable_spaces[NUM_SPACES]);
static int spaces_left(const char playable_spaces[NUM_SPACES]);
static bool ai_fart(int chance_to_fart);
static int pick_random_space(const char playable_spaces[NUM_SPACES]);
static int check_for_winning_move(const char playable_spaces[NUM_SPACES],
                                  char ai_side);
static int check_for_block(const char playable_spaces[NUM_SPACES], char side);
static char pick_side();


int main(int argc, char **argv) {
    // To-Do: Try the time(NULL) method for srand initialization and see if it
    // works the same
    time_t t;
    srand((unsigned)time(&t));
    char playable_spaces[NUM_SPACES] = "XXX"
                                       "XXX"
                                       "XXX";
    char *space_ptr = playable_spaces;

    // Game over splash
    const char *game_over_str = " Game Over! Any key to continue... ",
               *go_padding    = "                                   ";
    int game_over_len = strlen(game_over_str);

    //curses init
    initscr();
    cbreak();
    keypad(stdscr, 1);
    curs_set(INVISIBLE_CURSOR);
    start_color();
    init_pair(X_COLOR, COLOR_CYAN, COLOR_BLACK);
    init_pair(O_COLOR, COLOR_GREEN, COLOR_BLACK);
    init_pair(BG_COLOR, COLOR_YELLOW, COLOR_BLACK);
    noecho();

    // Main Menu outer loop
    // Main menu function quits or continues
    while (main_menu()) {
        // Init all spaces to blank
        init_spaces(space_ptr);
        // Player picks their side.
        char side = pick_side();
        // The inner, inner turn loop
        int game_over;
        do {
            // Paint the board state as it is that turn
            paint_board(playable_spaces);
            // Function that governs the turn cycle
            take_turn(side, space_ptr, playable_spaces);
            // Evaluate the board for game over state
            game_over = evaluate_board(playable_spaces);
        } while (!game_over);
        // paint the board with a splash on game over
        // so the player can evaluate the board for a moment
        paint_board(playable_spaces);
        int row, col;
        getmaxyx(stdscr, row, col);
        int y = row/2 + 6,
            x = col/2 - game_over_len/2;
        attron(COLOR_PAIR(BG_COLOR));
        mvprintw(y++, x, go_padding);
        mvprintw(y++, x, game_over_str);
        mvprintw(y, x, go_padding);
        refresh();
        getch();
        // call victory_splash with int game_over as a parameter
        // 1 = X wins, 2 = O wins, 3 = Tie
        victory_splash(game_over);
    }

    // end curses
    endwin();

    return 0;
}

static void init_spaces(char *space_ptr) {
    // init all the spaces to ' '
    memset(space_ptr, ' ', NUM_SPACES);
}

static void paint_board(char playable_spaces[NUM_SPACES]) {
    // paint the board and the playable spaces
    clear();
    paint_background();
    const char *break_lines = " ------- ",
               *play_lines  = " | | | | ",
               *padding     = "         ";
    int row, col;
    getmaxyx(stdscr, row, col);
    int y = row/2 - 4,
        len = strlen(padding),
        x = col/2 - len/2;
    attron(COLOR_PAIR(BG_COLOR));
    for (int k = 0; k < NUM_SPACES; k++) {
        // Paint the board itself without the pieces
        if (k == 0 || k == NUM_SPACES - 1)
            mvprintw(y + k, x, padding);
        else if (k%2 == 0)
            mvprintw(y + k, x, play_lines);
        else
            mvprintw(y + k, x, break_lines);
    }
    attroff(COLOR_PAIR(BG_COLOR));

    // insert Xs and Os:
    // First set the dynamic x and y coordinates based on terminal size
    int playable_x[NUM_SPACES], playable_y[NUM_SPACES];
    for (int i = 0; i < SQSIZE; i++) {
        int ycoord = y + 2*(i + 1);
        for (int j = 0; j < SQSIZE; j++) {
            int idx = SQSIZE*i + j;
            playable_x[idx] = x + 2*(j + 1);
            playable_y[idx] = ycoord;
        }
    }

    for (int k = 0; k < NUM_SPACES; k++) {
        // For each of the playable spaces, first set the color
        int color;
        switch (playable_spaces[k]) {
            case 'O':
                color = O_COLOR;
                break;
            case 'X':
                color = X_COLOR;
                break;
            default:
                color = BG_COLOR;
        }
        attron(COLOR_PAIR(color));
        // then insert the char for that space into the proper spot on the terminal
        mvaddch(playable_y[k], playable_x[k], playable_spaces[k]);
    }
    // refresh the screen
    refresh();
}

static void take_turn(char side, char *space_ptr,
                      char playable_spaces[NUM_SPACES]) {
    // using "side" to determine the order, call the functions to play a whole turn
    if (side == 'X') {
        player_turn(space_ptr, playable_spaces, side);
        paint_board(playable_spaces);
        if (spaces_left(playable_spaces)) {
            if (!(evaluate_board(playable_spaces))) {
                ai_turn(space_ptr, playable_spaces, side);
                paint_board(playable_spaces);
            }
        }
    }
    else if (side == 'O') {
        ai_turn(space_ptr, playable_spaces, side);
        paint_board(playable_spaces);
        if (spaces_left(playable_spaces)) {
            if (!(evaluate_board(playable_spaces))) {
                player_turn(space_ptr, playable_spaces, side);
                paint_board(playable_spaces);
            }
        }
    }
}

static bool main_menu() {
    const char *error_string = " Invalid Input! Any key to try again... ",
               *str1         = " NCURSES TIC TAC TOE (v2) ",
               *padding      = "                          ",
               *str2         = "    (P)lay or (Q)uit?     ";
    int len = strlen(str1),
        error_str_len = strlen(error_string);
    for (;;) {
        clear();
        // Takes user input and returns an int that quits or starts a game
        int row, col;

        paint_background();
        getmaxyx(stdscr, row, col);
        int y = row/2 - 2,
            x = col/2 - len/2;
        mvprintw(y++, x, padding);
        mvprintw(y++, x, str1);
        mvprintw(y++, x, padding);
        mvprintw(y++, x, str2);
        mvprintw(y++, x, padding);
        refresh();
        // get user input and return it
        switch (toupper(getch())) {
            case 'P':
                return true;
            case 'Q':
                return false;
            default:
                // call the function again if the input is bad
                x = col/2 - error_str_len/2;
                mvprintw(++y, x, error_string);
                getch();
        }
    }
}

static int evaluate_board(char playable_spaces[NUM_SPACES]) {
    // Evaluates the state of the playable spaces and either does nothing
    // or ends the game.
    // Check all the possible winning combinations:
    if (playable_spaces[0] == 'X' && playable_spaces[1] == 'X' && playable_spaces[2] == 'X') {
        return 1;
    }
    if (playable_spaces[3] == 'X' && playable_spaces[4] == 'X' && playable_spaces[5] == 'X') {
        return 1;
    }
    if (playable_spaces[6] == 'X' && playable_spaces[7] == 'X' && playable_spaces[8] == 'X') {
        return 1;
    }
    if (playable_spaces[0] == 'X' && playable_spaces[3] == 'X' && playable_spaces[6] == 'X') {
        return 1;
    }
    if (playable_spaces[1] == 'X' && playable_spaces[4] == 'X' && playable_spaces[7] == 'X') {
        return 1;
    }
    if (playable_spaces[2] == 'X' && playable_spaces[5] == 'X' && playable_spaces[8] == 'X') {
        return 1;
    }
    if (playable_spaces[0] == 'X' && playable_spaces[4] == 'X' && playable_spaces[8] == 'X') {
        return 1;
    }
    if (playable_spaces[2] == 'X' && playable_spaces[4] == 'X' && playable_spaces[6] == 'X') {
        return 1;
    }
    if (playable_spaces[0] == 'O' && playable_spaces[1] == 'O' && playable_spaces[2] == 'O') {
        return 2;
    }
    if (playable_spaces[3] == 'O' && playable_spaces[4] == 'O' && playable_spaces[5] == 'O') {
        return 2;
    }
    if (playable_spaces[6] == 'O' && playable_spaces[7] == 'O' && playable_spaces[8] == 'O') {
        return 2;
    }
    if (playable_spaces[0] == 'O' && playable_spaces[3] == 'O' && playable_spaces[6] == 'O') {
        return 2;
    }
    if (playable_spaces[1] == 'O' && playable_spaces[4] == 'O' && playable_spaces[7] == 'O') {
        return 2;
    }
    if (playable_spaces[2] == 'O' && playable_spaces[5] == 'O' && playable_spaces[8] == 'O') {
        return 2;
    }
    else if (playable_spaces[0] == 'O' && playable_spaces[4] == 'O' && playable_spaces[8] == 'O') {
        return 2;
    }
    else if (playable_spaces[2] == 'O' && playable_spaces[4] == 'O' && playable_spaces[6] == 'O') {
        return 2;
    }

    // Check all spaces for a tie
    int hits = 0;
    for (int i = 0; i < NUM_SPACES; i++)
        if (playable_spaces[i] != ' ')
            hits++;

    if (hits >= NUM_SPACES)
        return 3;

    return 0;
}

char pick_side() {
    const char *str1    = " Press 'X' for X, 'O' for O, or 'R' for random! ",
               *str2    = "        Good choice! Any key to continue...     ",
               *padding = "                                                ",
               *err_str = "      Invalid input! Any key to continue...     ";
    int len = strlen(str1);

    for (;;) {
        // Takes user input and returns the chosen side
        clear();
        paint_background();
        int row, col;
        getmaxyx(stdscr, row, col);
        int y = row / 2 - 2,
            x = col / 2 - len / 2;
        mvprintw(y++, x, padding);
        mvprintw(y++, x, str1);
        mvprintw(y++, x, padding);
        refresh();
        // Get user input for picking a side. 'R' is random.
        char input = toupper(getch());
        switch (input) {
            case 'X':
            case 'O': {
                mvprintw(y, x, str2);
                refresh();
                getch();
                return input;
            }
            case 'R': {
                bool pick = rand() % 2;
                if (pick)
                    input = 'X';
                else
                    input = 'O';
                mvprintw(y, x, str2);
                refresh();
                getch();
                return input;
            }
            default: {
                // Call the function again on bad input
                mvprintw(y, x, err_str);
                refresh();
                getch();
            }
        }
    }
}

static void victory_splash(int game_over_state) {
    // Takes the game over state and creates a victory splash
    const char *padding = "                                   ",
               *str1    = "              X Wins!              ",
               *str2    = "              O Wins!              ",
               *str3    = "         any key to continue...    ",
               *str4    = "             A tie game!           ";
    int len = strlen(padding);
    const char *vic_pointer;
    // To avoid code duplication, use a pointer to pick the right string
    switch (game_over_state) {
    case 1:
        vic_pointer = str1;
    case 2:
        vic_pointer = str2;
    case 3:
        vic_pointer = str4;
    }
    clear();
    paint_background();
    int row, col;
    getmaxyx(stdscr, row, col);
    int y = row/2 - 2,
        x = col/2 - len/2;
    mvprintw(y++, x, padding);
    mvprintw(y++, x, vic_pointer);
    mvprintw(y++, x, padding);
    mvprintw(y, x, str3);
    refresh();
    getch();
}

static void paint_background() {
    // Paints an elaborate flashy background
    int row, col;
    getmaxyx(stdscr, row, col);
    for (int y = 0; y <= row; y++) {
        for (int x = 0; x <= col; x++) {
            int color;
            char draw;
            switch (rand() % 3) {
                case 0:
                    color = X_COLOR;
                    draw = 'X';
                    break;
                case 1:
                    color = O_COLOR;
                    draw = 'O';
                    break;
                case 2:
                    color = BG_COLOR;
                    draw = ' ';
                    break;
            }
            attron(COLOR_PAIR(color));
            char draw_str[] = {draw, '\0'};
            mvprintw(y, x, draw_str);
            attroff(COLOR_PAIR(color));
        }
    }
    refresh();
}

static void player_turn(char *space_ptr, char playable_spaces[NUM_SPACES], char side) {
    // Function for the player turn
    char padding[] =  "                                                ";
    char str1[] =     "    Use arrow keys to move and 'P' to place!    ";
    char str3[] =     "                 Invalid input!                 ";
    char str4[] =     "             You can't move that way!           ";
    char str5[] =     "              Space already occupied!           ";
    int len = strlen(padding);
    int row, col, x, y;
    getmaxyx(stdscr, row, col);
    const int board_line_len = 9;
    const int board_lines = 9;
    y = row / 2 - board_line_len / 2;
    x = col / 2 - board_line_len / 2;
    // Use the same method of dynamically measuring where the spaces are at using
    // terminal size as in the paint_board() function.
    int playable_x[NUM_SPACES] = {x+2, x+4, x+6, x+2, x+4, x+6, x+2, x+4, x+6};
    int playable_y[NUM_SPACES] = {y+2, y+2, y+2, y+4, y+4, y+4, y+6, y+6, y+6};
    // The variables and mvprintw functions for the "info line"
    const int info_line_y = (row / 2 - board_lines / 2) + 10;
    const int info_line_x = col / 2 - len / 2;
    mvprintw(info_line_y - 1, info_line_x, padding);
    mvprintw(info_line_y, info_line_x, str1);
    mvprintw(info_line_y + 1, info_line_x, padding);
    // Using a loop and pointers to collect user input
    int moving = 1;
    int input;
    int *pos_x = &playable_x[0];
    int *pos_y = &playable_y[0];
    move(*pos_y, *pos_x);
    curs_set(1);
    refresh();
    while(moving) {
        // For each movement key, if the move is valid, use pointer
        // arithmetic to mov pos_x and pos_y around.
        input = toupper(getch());
        if (input == KEY_UP) {
            if (*pos_y != playable_y[0]) {
                pos_y -= 3;
                move(*pos_y, *pos_x);
                refresh();
            }
            else{
                mvprintw(info_line_y, info_line_x, str4);
                move(*pos_y, *pos_x);
                refresh();
            }
        }
        else if (input == KEY_DOWN) {
            if (*pos_y != playable_y[6]) {
                pos_y += 3;
                move(*pos_y, *pos_x);
                refresh();
            }
            else{
                mvprintw(info_line_y, info_line_x, str4);
                move(*pos_y, *pos_x);
                refresh();
            }
        }
        else if (input == KEY_LEFT) {
            if (*pos_x != playable_x[0]) {
                pos_x -= 1;
                move(*pos_y, *pos_x);
                refresh();
            }
            else{
                mvprintw(info_line_y, info_line_x, str4);
                move(*pos_y, *pos_x);
                refresh();
            }
        }
        else if (input == KEY_RIGHT) {
            if (*pos_x != playable_x[2]) {
                pos_x += 1;
                move(*pos_y, *pos_x);
                refresh();
            }
            else{
                mvprintw(info_line_y, info_line_x, str4);
                move(*pos_y, *pos_x);
                refresh();
            }
        }
        else if (input == 'P') {
            // I wanted to use KEY_ENTER instead of 'P' but it would not work
            // for some reason. When the user presses 'P' it checks where the
            // cursor is and sets the space_ptr to the appropriate index in the
            // playable_spaces array.
            if (*pos_y == playable_y[0] && *pos_x == playable_x[0]) {
                space_ptr = &playable_spaces[0];                
            }
            else if (*pos_y == playable_y[1] && *pos_x == playable_x[1]) {
                space_ptr = &playable_spaces[1];
            }
            else if (*pos_y == playable_y[2] && *pos_x == playable_x[2]) {
                space_ptr = &playable_spaces[2];
            }
            else if (*pos_y == playable_y[3] && *pos_x == playable_x[3]) {
                space_ptr = &playable_spaces[3];
            }
            else if (*pos_y == playable_y[4] && *pos_x == playable_x[4]) {
                space_ptr = &playable_spaces[4];
            }
            else if (*pos_y == playable_y[5] && *pos_x == playable_x[5]) {
                space_ptr = &playable_spaces[5];
            }
            else if (*pos_y == playable_y[6] && *pos_x == playable_x[6]) {
                space_ptr = &playable_spaces[6];
            }
            else if (*pos_y == playable_y[7] && *pos_x == playable_x[7]) {
                space_ptr = &playable_spaces[7];
            }
            else if (*pos_y == playable_y[8] && *pos_x == playable_x[8]) {
                space_ptr = &playable_spaces[8];
            }
            // Then checks to see if that space is empty.
            // If so it sets the color properly and then places the piece.
            if (*space_ptr == ' ') {
                if (side == 'X') {
                    attron(COLOR_PAIR(X_COLOR));
                    mvaddch(*pos_y, *pos_x, 'X');
                    attron(COLOR_PAIR(BG_COLOR));
                    *space_ptr = 'X';
                }
                else if (side == 'O') {
                    attron(COLOR_PAIR(O_COLOR));
                    mvaddch(*pos_y, *pos_x, 'O');
                    attron(COLOR_PAIR(BG_COLOR));
                    *space_ptr = 'O';
                }
                refresh();
                moving = 0;
            }
            else{
                mvprintw(info_line_y, info_line_x, str5);
                move(*pos_y, *pos_x);
                refresh();
            }
        }
        else{
            mvprintw(info_line_y, info_line_x, str3);
            move(*pos_y, *pos_x);
            refresh();
        }
    }
}

//////////////////////////////////////////////////////////////////////////////////////
// Begin AI Logic ////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////

static void ai_turn(char *space_ptr, char playable_spaces[NUM_SPACES], char side) {
    // wrapper for the AI turn
    /*
        Note: Since it is easy to accidentally create an unbeatable AI for tic tac toe
              I am building into the AI the chance for it to not make the optimal move.
                    This intentional fuzziness will be built into the functions that check for
                    avaialable spaces. When they find an optimal move they may just decide
                    to return 0 anyway.

        P-Code:
        if center square not taken, take center square 70% of the time;
        else:
            if opponent about to win, block them 90% of the time;
            elif self about to win take winning spot 90% of the time;
            else pick a random open spot;
    */
    // Picking the character for the AI to use in its calculations
    char ai_side;
    if (side == 'X')
        ai_side = 'O';
    else
        ai_side = 'X';

    // Check the board state with a few functions.
    int picked_space = pick_ai_space(playable_spaces, 10, 30, side, ai_side);
    space_ptr = &playable_spaces[picked_space];
    if (ai_side == 'X')
        attron(COLOR_PAIR(X_COLOR));
    else
        attron(COLOR_PAIR(O_COLOR));
    *space_ptr = ai_side;
    attron(COLOR_PAIR(BG_COLOR));
}

static int pick_ai_space(const char playable_spaces[NUM_SPACES],
                         int chance_to_fart_big_move,
                         int chance_to_fart_center,
                         char side, char ai_side) {
    int can_block_opponent = check_for_block(playable_spaces, side),
        can_winning_move = check_for_winning_move(playable_spaces, ai_side);

    // Flow through the decision making logic applying the functions and
    // checking for a fart
    if (playable_spaces[4] == ' ' &&
        !(ai_fart(chance_to_fart_center)))
        return 4;
    if (can_winning_move) {
        if (!(ai_fart(chance_to_fart_big_move)))
            return can_winning_move;
        return pick_random_space(playable_spaces);
    }
    if (can_block_opponent) {
        if (!(ai_fart(chance_to_fart_big_move)))
            return can_block_opponent;
        return pick_random_space(playable_spaces);
    }
    return pick_random_space(playable_spaces);
}

static bool ai_fart(int chance_to_fart) {
    // Takes the fart chance and returns 1 if the AI blows the move, 0 otherwise
    int roll = rand() % 100 + 1;
    return roll < chance_to_fart;
}

static int pick_random_space(const char playable_spaces[NUM_SPACES]) {
    // Returns a random open space on the board
    for (;;) {
        int roll = rand() % NUM_SPACES;
        if (playable_spaces[roll] == ' ')
            return roll;
    }
}

static int check_for_winning_move(const char playable_spaces[NUM_SPACES], char ai_side) {
    // Checks to see if the AI can win the game with a final move and returns the
    // index of the valid move if TRUE, returns 0 if FALSE
    int pick;
    bool picked = false;
    for (int space = 0; space < NUM_SPACES; space++) {
        // For each space: Check to see if it is a potential winning space and if so
        // switch "picked" to 1 and set "pick" to the winning index
        switch (space) {
            case 0:
                if (playable_spaces[space] == ' ') {
                    if (playable_spaces[1] == ai_side && playable_spaces[2] == ai_side) {
                        pick = space;
                        picked = true;
                    }
                    else if (playable_spaces[3] == ai_side && playable_spaces[6] == ai_side) {
                        pick = space;
                        picked = true;
                    }
                    else if (playable_spaces[4] == ai_side && playable_spaces[8] == ai_side) {
                        pick = space;
                        picked = true;
                    }
                }
                break;
            case 1:
                if (playable_spaces[space] == ' ') {
                    if (playable_spaces[0] == ai_side && playable_spaces[2] == ai_side) {
                        pick = space;
                        picked = true;
                    }
                    else if (playable_spaces[4] == ai_side && playable_spaces[7] == ai_side) {
                        pick = space;
                        picked = true;
                    }
                }
                break;
            case 2:
                if (playable_spaces[space] == ' ') {
                    if (playable_spaces[1] == ai_side && playable_spaces[0] == ai_side) {
                        pick = space;
                        picked = true;
                    }
                    else if (playable_spaces[4] == ai_side && playable_spaces[6] == ai_side) {
                        pick = space;
                        picked = true;
                    }
                    else if (playable_spaces[5] == ai_side && playable_spaces[8] == ai_side) {
                        pick = space;
                        picked = true;
                    }
                }
                break;
            case 3:
                if (playable_spaces[space] == ' ') {
                    if (playable_spaces[4] == ai_side && playable_spaces[5] == ai_side) {
                        pick = space;
                        picked = true;
                    }
                    else if (playable_spaces[0] == ai_side && playable_spaces[6] == ai_side) {
                        pick = space;
                        picked = true;
                    }
                }
                break;
            case 4:
                if (playable_spaces[space] == ' ') {
                    if (playable_spaces[1] == ai_side && playable_spaces[7] == ai_side) {
                        pick = space;
                        picked = true;
                    }
                    else if (playable_spaces[3] == ai_side && playable_spaces[5] == ai_side) {
                        pick = space;
                        picked = true;
                    }
                    else if (playable_spaces[0] == ai_side && playable_spaces[8] == ai_side) {
                        pick = space;
                        picked = true;
                    }
                    else if (playable_spaces[6] == ai_side && playable_spaces[2] == ai_side) {
                        pick = space;
                        picked = true;
                    }
                }
                break;
            case 5:
                if (playable_spaces[space] == ' ') {
                    if (playable_spaces[8] == ai_side && playable_spaces[2] == ai_side) {
                        pick = space;
                        picked = true;
                    }
                    else if (playable_spaces[3] == ai_side && playable_spaces[4] == ai_side) {
                        pick = space;
                        picked = true;
                    }
                }
                break;
            case 6:
                if (playable_spaces[space] == ' ') {
                    if (playable_spaces[4] == ai_side && playable_spaces[2] == ai_side) {
                        pick = space;
                        picked = true;
                    }
                    else if (playable_spaces[7] == ai_side && playable_spaces[8] == ai_side) {
                        pick = space;
                        picked = true;
                    }
                    else if (playable_spaces[3] == ai_side && playable_spaces[0] == ai_side) {
                        pick = space;
                        picked = true;
                    }
                }
                break;
            case 7:
                if (playable_spaces[space] == ' ') {
                    if (playable_spaces[6] == ai_side && playable_spaces[8] == ai_side) {
                        pick = space;
                        picked = true;
                    }
                    else if (playable_spaces[4] == ai_side && playable_spaces[1] == ai_side) {
                        pick = space;
                        picked = true;
                    }
                }
                break;
            case 8:
                if (playable_spaces[space] == ' ') {
                    if (playable_spaces[5] == ai_side && playable_spaces[2] == ai_side) {
                        pick = space;
                        picked = true;
                    }
                    else if (playable_spaces[4] == ai_side && playable_spaces[0] == ai_side) {
                        pick = space;
                        picked = true;
                    }
                    else if (playable_spaces[7] == ai_side && playable_spaces[6] == ai_side) {
                        pick = space;
                        picked = true;
                    }
                }
                break;
        }
    }
    // return winning index if any
    if (picked)
        return pick;
    return 0;
}

static int check_for_block(const char playable_spaces[NUM_SPACES], char side) {
    // Checks to see if the AI can block the player from winning the game with a final move
    // and returns the index of the valid move if TRUE, returns 0 if FALSE
    // Note: I am sure there is a way to combine this this function with the
    //  check_for_winning_move() function in order to avoid code duplication, probably using
    //  one more parameter as a switch of some kind. I'd be open to examples of how to do that.
    int pick;
    bool picked = false;
    for (int space = 0; space < NUM_SPACES; space++) {
        // For each space: Check to see if it is a potential winning space and if so
        // switch "picked" to 1 and set "pick" to the winning index
        switch (space) {
            case 0:
                if (playable_spaces[space] == ' ') {
                    if (playable_spaces[1] == side && playable_spaces[2] == side) {
                        pick = space;
                        picked = true;
                    }
                    else if (playable_spaces[3] == side && playable_spaces[6] == side) {
                        pick = space;
                        picked = true;
                    }
                    else if (playable_spaces[4] == side && playable_spaces[8] == side) {
                        pick = space;
                        picked = true;
                    }
                }
                break;
            case 1:
                if (playable_spaces[space] == ' ') {
                    if (playable_spaces[0] == side && playable_spaces[2] == side) {
                        pick = space;
                        picked = true;
                    }
                    else if (playable_spaces[4] == side && playable_spaces[7] == side) {
                        pick = space;
                        picked = true;
                    }
                }
                break;
            case 2:
                if (playable_spaces[space] == ' ') {
                    if (playable_spaces[1] == side && playable_spaces[0] == side) {
                        pick = space;
                        picked = true;
                    }
                    else if (playable_spaces[4] == side && playable_spaces[6] == side) {
                        pick = space;
                        picked = true;
                    }
                    else if (playable_spaces[5] == side && playable_spaces[8] == side) {
                        pick = space;
                        picked = true;
                    }
                }
                break;
            case 3:
                if (playable_spaces[space] == ' ') {
                    if (playable_spaces[4] == side && playable_spaces[5] == side) {
                        pick = space;
                        picked = true;
                    }
                    else if (playable_spaces[0] == side && playable_spaces[6] == side) {
                        pick = space;
                        picked = true;
                    }
                }
                break;
            case 4:
                if (playable_spaces[space] == ' ') {
                    if (playable_spaces[1] == side && playable_spaces[7] == side) {
                        pick = space;
                        picked = true;
                    }
                    else if (playable_spaces[3] == side && playable_spaces[5] == side) {
                        pick = space;
                        picked = true;
                    }
                    else if (playable_spaces[0] == side && playable_spaces[8] == side) {
                        pick = space;
                        picked = true;
                    }
                    else if (playable_spaces[6] == side && playable_spaces[2] == side) {
                        pick = space;
                        picked = true;
                    }
                }
                break;
            case 5:
                if (playable_spaces[space] == ' ') {
                    if (playable_spaces[8] == side && playable_spaces[2] == side) {
                        pick = space;
                        picked = true;
                    }
                    else if (playable_spaces[3] == side && playable_spaces[4] == side) {
                        pick = space;
                        picked = true;
                    }
                }
                break;
            case 6:
                if (playable_spaces[space] == ' ') {
                    if (playable_spaces[4] == side && playable_spaces[2] == side) {
                        pick = space;
                        picked = true;
                    }
                    else if (playable_spaces[7] == side && playable_spaces[8] == side) {
                        pick = space;
                        picked = true;
                    }
                    else if (playable_spaces[3] == side && playable_spaces[0] == side) {
                        pick = space;
                        picked = true;
                    }
                }
                break;
            case 7:
                if (playable_spaces[space] == ' ') {
                    if (playable_spaces[6] == side && playable_spaces[8] == side) {
                        pick = space;
                        picked = true;
                    }
                    else if (playable_spaces[4] == side && playable_spaces[1] == side) {
                        pick = space;
                        picked = true;
                    }
                }
                break;
            case 8:
                if (playable_spaces[space] == ' ') {
                    if (playable_spaces[5] == side && playable_spaces[2] == side) {
                        pick = space;
                        picked = true;
                    }
                    else if (playable_spaces[4] == side && playable_spaces[0] == side) {
                        pick = space;
                        picked = true;
                    }
                    else if (playable_spaces[7] == side && playable_spaces[6] == side) {
                        pick = space;
                        picked = true;
                    }
                }
                break;
        }
    }
    // return winning index if any
    if (picked)
        return pick;
    return 0;
}

///////////////////////////////////////////////////////////////////////////////////
// End AI Logic ///////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////

static int spaces_left(const char playable_spaces[NUM_SPACES]) {
    // Returns 0 if no spaces left
    int hits = 0;
    for (int k = 0; k < NUM_SPACES; k++)
        if (playable_spaces[k] == ' ')
            hits++;
    return hits;
}
票数 8
EN

Code Review用户

发布于 2018-12-30 04:01:30

,我制作了这个Community,因为我的答案围绕着一个例子,即那些对C更了解的人可能会改进和扩展。欢迎改进!

实践干码

您在上一篇评论中收到了一段反馈,我认为认真对待它将极大地改进您的编码风格:练习干的代码。你永远不应该有代码,基本上可以复制粘贴,只需几个值的变化。学习如何避免这种情况将使您的代码更短、更容易阅读和更易于维护。您通常可以通过进一步模块化代码来避免重复,通常是通过创建更多的函数。

为了演示我的意思,让我们以您的代码中的一部分作为案例研究:

案例研究:双赢条件

代码语言:javascript
复制
    if(playable_spaces[0] == 'X' && playable_spaces[1] == 'X' && playable_spaces[2] == 'X'){
        return 1;
    }else if(playable_spaces[3] == 'X' && playable_spaces[4] == 'X' && playable_spaces[5] == 'X'){
        return 1;
    }else if(playable_spaces[6] == 'X' && playable_spaces[7] == 'X' && playable_spaces[8] == 'X'){
        return 1;
    }else if(playable_spaces[0] == 'X' && playable_spaces[3] == 'X' && playable_spaces[6] == 'X'){
        return 1;
    }else if(playable_spaces[1] == 'X' && playable_spaces[4] == 'X' && playable_spaces[7] == 'X'){
        return 1;
    }else if(playable_spaces[2] == 'X' && playable_spaces[5] == 'X' && playable_spaces[8] == 'X'){
        return 1;
    }else if(playable_spaces[0] == 'X' && playable_spaces[4] == 'X' && playable_spaces[8] == 'X'){
        return 1;
    }else if(playable_spaces[2] == 'X' && playable_spaces[4] == 'X' && playable_spaces[6] == 'X'){
        return 1;
    }else if(playable_spaces[0] == 'O' && playable_spaces[1] == 'O' && playable_spaces[2] == 'O'){
        return 2;
    }else if(playable_spaces[3] == 'O' && playable_spaces[4] == 'O' && playable_spaces[5] == 'O'){
        return 2;
    }else if(playable_spaces[6] == 'O' && playable_spaces[7] == 'O' && playable_spaces[8] == 'O'){
        return 2;
    }else if(playable_spaces[0] == 'O' && playable_spaces[3] == 'O' && playable_spaces[6] == 'O'){
        return 2;
    }else if(playable_spaces[1] == 'O' && playable_spaces[4] == 'O' && playable_spaces[7] == 'O'){
        return 2;
    }else if(playable_spaces[2] == 'O' && playable_spaces[5] == 'O' && playable_spaces[8] == 'O'){
        return 2;
    }else if(playable_spaces[0] == 'O' && playable_spaces[4] == 'O' && playable_spaces[8] == 'O'){
        return 2;
    }else if(playable_spaces[2] == 'O' && playable_spaces[4] == 'O' && playable_spaces[6] == 'O'){
        return 2;
    }

我甚至不理解这段代码做什么,我就能看出它不完全正确,因为它有太多的重复。

如果有多个返回相同内容的条件分支,则可以将它们与||或运算符组合起来(下面只显示了'X'代码):

代码语言:javascript
复制
    if((playable_spaces[0] == 'X' && playable_spaces[1] == 'X' && playable_spaces[2] == 'X')
    || (playable_spaces[3] == 'X' && playable_spaces[4] == 'X' && playable_spaces[5] == 'X')
    || (playable_spaces[6] == 'X' && playable_spaces[7] == 'X' && playable_spaces[8] == 'X')
    || (playable_spaces[0] == 'X' && playable_spaces[3] == 'X' && playable_spaces[6] == 'X')
    || (playable_spaces[1] == 'X' && playable_spaces[4] == 'X' && playable_spaces[7] == 'X')
    || (playable_spaces[2] == 'X' && playable_spaces[5] == 'X' && playable_spaces[8] == 'X')
    || (playable_spaces[0] == 'X' && playable_spaces[4] == 'X' && playable_spaces[8] == 'X')
    || (playable_spaces[2] == 'X' && playable_spaces[4] == 'X' && playable_spaces[6] == 'X')) {
        return 1;
    }

我跳过复制'O‘代码,因为它与'X’代码完全相同,除了更改'O‘之外。这也是不必要的重复。您可以创建一个以'X‘和'O’作为char参数的函数,并返回上述||序列。

--一种更简单的方法

首先,假设我们有一些常量:

代码语言:javascript
复制
#define SIDE_LEN 3
#define NUM_SQUARES (SIDE_LEN*SIDE_LEN)

现在,让我们创建一个定义如下的Board结构:

代码语言:javascript
复制
typedef struct board {
    char spaces[NUM_SQUARES];
    int available;
} Board;

spaces包含X0或空的令牌。值available只保存空方格的数量。这个结构的初始化是显而易见的,应该在每个游戏之前完成。

现在,我们可以轻松地创建一个助手函数:

代码语言:javascript
复制
char board_gettoken(const Board *b, int row, int col) {
    return b->spaces[row + col * SIDE_LEN];
}

如果我们描述的是如何赢给一个孩子,我们可以说,在任何地方,我们连续三次,一列,或者沿着对角线,那就是胜利。如果所有的方块都填满了(即available == 0),并且没有赢家,那就是平局。最后,请注意,只有刚刚移动的球员才有可能获胜。我们可以使用这些简单的事实来编写一个非常简单的board_evaluate函数:

代码语言:javascript
复制
// given that `token` just moved, return 
//  0 if no winner
//  1 if token just won
//  2 if tie
int board_evaluate(const Board *b, char token) {
    bool diag_winner = true;
    bool rev_diag_winner = true;
    for (int i=0; i < SIDE_LEN; ++i) {
        diag_winner &= board_gettoken(b, i, i) == token;
        rev_diag_winner &= board_gettoken(b, i, SIDE_LEN-1-i) == token;
    }
    if (diag_winner || rev_diag_winner) {
        return 1;
    }
    for (int i=0; i < SIDE_LEN; ++i) {
        bool row_winner = true;
        bool col_winner = true;
        for (int j=0; j < SIDE_LEN; ++j) {
            col_winner &= board_gettoken(b, i, j) == token;
            row_winner &= board_gettoken(b, j, i) == token;
        }
        if (row_winner || col_winner) {
            return 1;
        }
    }
    // must be a non-win or tie
    return b->available == 0 ? 2 : 0;
}

最后,请注意,如果我们想要创建4x4或9x9版本,只需要更改SIDE_LEN常量值。此外,在处理board_板的函数前加上前缀也很方便,因此很容易看出它们是相关的。在面向对象的语言(如C++ )中,这些可能是成员函数。

使用干代码实现更好的AI

一个人可以利用上面的功能,写一个更快,更好,更短的人工智能播放器以及。首先,我们定义了另一个帮助函数:

代码语言:javascript
复制
int board_makemove(Board *b, int i, int j, char token) {
    board_settoken(b, i, j, token);
    --b->available;
    return board_evaluate(b, token);
}

当我们做了最后的选择,这个函数使用board_settoken更新数据结构,减少可用的平方计数,然后返回板评估的结果。我们没有减少available例程中的board_settoken计数是有原因的,我们将在以下函数中看到这一点:

代码语言:javascript
复制
int ai_turn(Board *b, char token) {
    const char antitoken = token == 'X' ? 'O' : 'X';
    // first look for a move that would win
    for (int i=0; i < SIDE_LEN; ++i) {
        for (int j=0; j < SIDE_LEN; ++j) {
            if (board_gettoken(b, i, j) == ' ') {
                board_settoken(b, i, j, token);
                int status = board_evaluate(b, token);
                if (status) {
                    --b->available;
                    return status;
                } else {
                    board_settoken(b, i, j, ' ');  // undo move
                }
            }
        }
    }
    // next look for a move that would block
    for (int i=0; i < SIDE_LEN; ++i) {
        for (int j=0; j < SIDE_LEN; ++j) {
            if (board_gettoken(b, i, j) == ' ') {
                board_settoken(b, i, j, antitoken);
                if (board_evaluate(b, antitoken) == 1) {
                    return board_makemove(b, i, j, token);
                } else {
                    board_settoken(b, i, j, ' ');  // undo move
                }
            }
        }
    }
    // look for center
    { 
        int i = SIDE_LEN/2;
        if (board_gettoken(b, i, i) == ' ') {
            return board_makemove(b, i, i, token);
        }
    }
    // look for corner 
    for (int i=0; i < SIDE_LEN; i += (SIDE_LEN-1)) {  
        for (int j=0; j < SIDE_LEN; j += (SIDE_LEN-1)) {  
            if (board_gettoken(b, i, j) == ' ') {
                return board_makemove(b, i, j, token);
            }
        }
    }
    // choose first available
    for (int i=0; i < SIDE_LEN; ++i) {
        for (int j=0; j < SIDE_LEN; ++j) {
            if (board_gettoken(b, i, j) == ' ') {
                return board_makemove(b, i, j, token);
            }
        }
    }
    return 0;
}

这是一个非常简单的代码,易于遵循,但它是非常胜任的游戏。还有更多的机会来干燥上面的代码。

票数 8
EN

Code Review用户

发布于 2018-12-30 04:58:05

小评论

在代码的两次迭代中,我注意到您必须转发声明每个函数。这是不可取的。最好最后声明main(),从而消除转发声明的需要。毕竟,想象一下大型项目所需的前向声明。

在此之后,您还可能希望开始将您的逻辑分离为模块化文件,然后您可以对其进行#include。小逻辑连接的函数群,如#defines、structs等。

也不要这样做:

代码语言:javascript
复制
// ncurses for, well, ncurses
#include <ncurses.h>
// time for the random seed
#include <time.h>
// string.h for strlen() 
#include <string.h>
// stdlib and stdio because why not
#include <stdlib.h>
#include <stdio.h>
// ctype.h for toupper()
#include <ctype.h>

如果你想评论每一个包含是为了什么(我觉得没有必要,但我可能错了),至少把它放在一边,以便扫描#includes是可读的。就像这样:

代码语言:javascript
复制
#include <ncurses.h> // for, well, ncurses
#include <time.h> // for random seed
#include <string.h> // for strlen()
#include <stdlib.h> // why not?
#include <stdio.h> // why not?
#include <ctype.h> // for toupper()

永远不要,永远不要包含你不需要的标题。(我没有阅读足够的代码,但评论“为什么不呢?”让我觉得你不知道你是否需要stdlibstdio,这是一个很糟糕的理由。)需要时,只需添加标题即可。

票数 8
EN
页面原文内容由Code Review提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://codereview.stackexchange.com/questions/210577

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档