C语言 极大极小算法行为异常

mzsu5hc0  于 2022-12-17  发布在  其他
关注(0)|答案(1)|浏览(114)

我正在做作业。我想创建一个由计算机模拟和玩的游戏。这是一个逻辑游戏,目标是收集最多的具有最高价值的代币。程序开始时要求输入以下格式(“END”表示输入结束,正在删除它):

N:1,2 
W:3,5
E:9,1,1,1
S:1,7
END

字母代表东、西、北、南等方向。方向后面的数字是有价值的代币。您只能从方向的末端拾取代币。收集到最有价值代币的玩家获胜。
我需要使游戏发挥自己的最佳方式,发现极大极小算法。但我完全不知道如何正确地实现它。
我在请求一些善良的灵魂,帮助我使它正确工作。至少给予一些提示:)
这是我尝试过的。它在某种程度上起作用,但不是最优的方式。例如,在我提供的输入中,最优的结果是:

A/B: 15/16

但我得到了:

A/B: 14/17

我的密码在这里。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <limits.h>
#include <math.h>
#define MAX_TOKENS 100

struct Game
{
    //lists with the values ​​of the tokens on the individual arms of the cross
    int *north;
    int *west;
    int *east;
    int *south;
    //lengths of token value lists
    int north_size;
    int west_size;
    int east_size;
    int south_size;
    //scores of players A and B
    int score_a;
    int score_b;
};
void remove_token(struct Game *game, int player, char direction)
{
    //variable for the token taken
    int token;

    //according to the direction, remove the token and add its value to the player's score
    switch (direction)
    {
    case 'N':
        //remove the token from the N arm
        token = game->north[game->north_size - 1];
        //reduce the length of the token value list by 1
        game->north_size--;
        //add the chip value to the player's score
        if (player == 0)
            game->score_a += token;
        else
            game->score_b += token;
        break;
    case 'W':
        //remove the token from the W arm
        token = game->west[game->west_size - 1];
        //reduce the length of the token value list by 1
        game->west_size--;
        //add the chip value to the player's score
        if (player == 0)
            game->score_a += token;
        else
            game->score_b += token;
        break;
    case 'E':
        //remove the token from the E arm
        token = game->east[game->east_size - 1];
        //reduce the length of the token value list by 1
        game->east_size--;
        //add the chip value to the player's score
        if (player == 0)
            game->score_a += token;
        else
            game->score_b += token;
        break;
    case 'S':
        //remove the token from the S arm
        token = game->south[game->south_size - 1];
        //reduce the length of the token value list by 1
        game->south_size--;
        //add the chip value to the player's score
        if (player == 0)
            game->score_a += token;
        else
            game->score_b += token;
        break;
    default:
        //throw an error if the player entered an invalid direction
        printf("Neplatný směr pro odebrání žetonu!\n");
    }
}
char minimax(struct Game *game, int depth, int player, int alpha, int beta)
{
    //if all arms are empty or we have reached the maximum depth, return the best direction
    if (game->north_size == 0 && game->west_size == 0 && game->east_size == 0 && game->south_size == 0 || depth == 0)
        return 'X';

    //the best move value for player A
    int best_score_a = INT_MIN;
    //the best move value for player B
    int best_score_b = INT_MAX;
    //direction for best move
    char best_direction;

    //go through all arms to find the best move
    if (game->north_size > 0)
    {
        //copy the game state
        struct Game game_copy = *game;
        //remove the token from the N arm
        remove_token(&game_copy, player, 'N');
        //find out the best move for your opponent
        int score = minimax(&game_copy, depth - 1, player == 0 ? 1 : 0, alpha, beta);
        //update the best move for player A
        if (player == 0 && score > best_score_a)
        {
            best_score_a = score;
            best_direction = 'N';
        }
        //update the best move for player B
        if (player == 1 && score < best_score_b)
        {
            best_score_b = score;
            best_direction = 'N';
        }
        //update alpha and beta
        if (player == 0)
            alpha = fmax(alpha, score);
        else
            beta = fmin(beta, score);
        //if beta is less than alpha, we end traversing the tree
        if (beta <= alpha)
            return best_direction;
    }
      if (game->west_size > 0)
    {
        //copy the game state
        struct Game game_copy = *game;
        //remove the token from the W arm
        remove_token(&game_copy, player, 'W');
        //find out the best move for your opponent
        int score = minimax(&game_copy, depth - 1, player == 0 ? 1 : 0, alpha, beta);
        //update the best move for player A
                if (player == 0 && score > best_score_a)
        {
            best_score_a = score;
            best_direction = 'W';
        }
        //update the best move for player B
        if (player == 1 && score < best_score_b)
        {
            best_score_b = score;
            best_direction = 'W';
        }
        //update alpha and beta
        if (player == 0)
            alpha = fmax(alpha, score);
        else
            beta = fmin(beta, score);
        //if beta is less than alpha, we end traversing the tree
        if (beta <= alpha)
            return best_direction;
    }
    if (game->east_size > 0)
    {
        //copy the game state
        struct Game game_copy = *game;
        //remove the token from the E arm
        remove_token(&game_copy, player, 'E');
        //find out the best move for your opponent
        int score = minimax(&game_copy, depth - 1, player == 0 ? 1 : 0, alpha, beta);
        //update the best move for player A
        if (player == 0 && score > best_score_a)
        {
            best_score_a = score;
            best_direction = 'E';
        }
        //update the best move for player B
        if (player == 1 && score < best_score_b)
        {
            best_score_b = score;
            best_direction = 'E';
        }
        //update alpha and beta
        if (player == 0)
            alpha = fmax(alpha, score);
        else
            beta = fmin(beta, score);
        //if beta is less than alpha, we end traversing the tree
        if (beta <= alpha)
            return best_direction;
    }
    if (game->south_size > 0)
    {
        //copy the game state
        struct Game game_copy = *game;
        //remove the token from the S arm
        remove_token(&game_copy, player, 'S');
        //find out the best move for your opponent
        int score = minimax(&game_copy, depth - 1, player == 0 ? 1 : 0, alpha, beta);
        //update the best move for player A
        if (player == 0 && score > best_score_a)
        {
            best_score_a = score;
            best_direction = 'S';
        }
        //update as soon as possible
                if (player == 1 && score < best_score_b)
        {
            best_score_b = score;
            best_direction = 'S';
        }
        //update alpha and beta
        if (player == 0)
            alpha = fmax(alpha, score);
        else
            beta = fmin(beta, score);
        //if beta is less than alpha, we end traversing the tree
        if (beta <= alpha)
            return best_direction;
    }

    //return the best move
    return player == 0 ? best_direction : best_direction;
}
void read_tokens(int *north, int *west, int *east, int *south, int *north_size, int *west_size, int *east_size, int *south_size)
{
    //buffer for reading in input
    char buffer[MAX_TOKENS];

    //read in the input line by line
    while (fgets(buffer, MAX_TOKENS, stdin) != NULL)
    {
        //remove the newline character from the end of the line
        buffer[strcspn(buffer, "\n")] = 0;
        //check for the "END" string to end the input
        if (strcmp(buffer, "END") == 0)
            break;
        //split the line at the colon character
        char *direction = strtok(buffer, ":");
        char *tokens = strtok(NULL, ":");

        //split the tokens at each comma
        char *token = strtok(tokens, ",");

        //determine the direction and store the tokens in the appropriate array
        if (strcmp(direction, "N") == 0)
        {
            while (token != NULL)
            {
                north[*north_size] = atoi(token);
                (*north_size)++;
                token = strtok(NULL, ",");
            }
        }
        else if (strcmp(direction, "W") == 0)
        {
            while (token != NULL)
            {
                west[*west_size] = atoi(token);
                (*west_size)++;
                token = strtok(NULL, ",");
            }
        }
        else if (strcmp(direction, "E") == 0)
        {
            while (token != NULL)
            {
                east[*east_size] = atoi(token);
                (*east_size)++;
                token = strtok(NULL, ",");
            }
        }
        else if (strcmp(direction, "S") == 0)
        {
            while (token != NULL)
            {
                south[*south_size] = atoi(token);
                (*south_size)++;
                token = strtok(NULL, ",");
            }
        }
        else
        {
            //invalid direction = error
            printf("Nespravny vstup.\n");
        }
    }
}

void print_progress(struct Game *game, int player, char direction)
{
    char letter_player = ' ';
    if (player == 0)
    {
        letter_player = 'A';
    }
    else
        letter_player = 'B';
    //printing of individual steps
    switch (direction)
    {
    case 'N':
        printf("%c: %c[%d] (%d)\n", letter_player, direction, game->north_size, game->north[game->north_size - 1]);
        break;
    case 'W':
        printf("%c: %c[%d] (%d)\n", letter_player, direction, game->west_size, game->west[game->west_size - 1]);
        break;
    case 'E':
        printf("%c: %c[%d] (%d)\n", letter_player, direction, game->east_size, game->east[game->east_size - 1]);
        break;
    case 'S':
        printf("%c: %c[%d] (%d)\n", letter_player, direction, game->south_size, game->south[game->south_size - 1]);
        break;
    default:
        break;
    }
}

void play(struct Game *game, int depth)
{
    //variable for current player (A or B)
    int player = 0;

    //until all chips are taken, alternate players taking chips
    while (game->north_size > 0 || game->west_size > 0 || game->east_size > 0 || game->south_size > 0)
    {
        //player A
        if (player == 0)
        {
            //function on the selection of a token
            char direction = minimax(game, depth, 0, INT_MIN, INT_MAX);
            print_progress(game, player, direction);
            //remove the token from the game
            remove_token(game, player, direction);
        }
        //player B
        else
        {
            //function on the selection of a token
            char direction = minimax(game, depth, 1, INT_MIN, INT_MAX);
            print_progress(game, player, direction);
            //remove the token from the game
            remove_token(game, player, direction);
        }

        //switch players
        player = (player + 1) % 2;
    }
}
int main(void)
{
    //field for token values
    int north[MAX_TOKENS], west[MAX_TOKENS], east[MAX_TOKENS], south[MAX_TOKENS];
    //sizes of token value fields
    int north_size = 0, west_size = 0, east_size = 0, south_size = 0;
    printf("Input:\n");
    //fetch token values ​​from input
    read_tokens(north, west, east, south, &north_size, &west_size, &east_size, &south_size);

    //creating a game
    struct Game game;
    game.north = north;
    game.west = west;
    game.east = east;
    game.south = south;
    game.north_size = north_size;
    game.west_size = west_size;
    game.east_size = east_size;
    game.south_size = south_size;
    game.score_a = 0;
    game.score_b = 0;
    //set the maximum depth of the minimax search tree
    int depth = 1;
    //start the game using the play() function
    play(&game, depth);

    //evaluation of the result of the game

    printf("Celkem A/B: %d/%d\n", game.score_a, game.score_b);

    return 0;
}
ttygqcqt

ttygqcqt1#

这不是一个答案。不过,操作员确实问了一些“提示”。下面是一些:
1.不要为您可以/应该实现的单个函数(fmax())拖入浮点库(math. h),特别是在处理整数值和结果时。
下面是两个返回两个整数的最小值或最大值的“无分支”语句:i & j公司

int minVal = j ^ ((i^j) & -(i<j));
int maxVal = j ^ ((i^j) & -(i>j));

1.您可以安全地从数据文件中删除ENDfgets()将找到4行,然后返回NULL,完成初始值的加载。
1.初始值是一个ASCII数字。与调用atoi()不同,只需从每个ASCII数字中减去ASCII字符'0'就可以得到想要的(二进制)整数值。(您可以考虑在初始数据中使用“A”-“Z”,而不是“0”-“9”,这样既可以增加点数的范围,又可以模糊赢得游戏的最佳路径。
3a)代替“N:1,5,6,4”,数据文件可以包含“N1564”,从而避免使用strtok()的需要...简单地按顺序使用每个字符。
1.作为一个初学者,你编写了一个包含4个指南针点的“字面”游戏,这是可以理解的,你编写的代码很难改变为有5或6个(或更多)堆栈的值可供选择。
如果您使用4个元素的数组,而不是名为“north...”、“west...”等的离散变量,那么使源代码变大的大多数复制/粘贴/适配操作将分解为处理4个元素中的每一个(或5个或6个)“栈”。编码的艺术是在“使某些东西工作”的驱动力和代码重用的必要性之间保持平衡。2寻找冗余并努力消除冗余。
1.代码在fgets()填满输入缓冲区后删除了尾随的'\n',然后继续将字符数组转换为离散整数值数组,最小最大函数在玩游戏时递归地修剪这些数组的副本。
考虑保留并使用原始字符数组(字符串)。当“用户”选择最后一个“字符”时(堆栈顶部)作为其“移动”,简单地缩短该叠层的串。(使用C的string.h字符串处理功能,而不是自己使用int型数组。)(字符串""没有可用的标记。您的代码不需要为每个“direction”维护计数器。)
1.(这可能是最简单的。)当您开始使用4个数组时--“N”、“W”、“E”和“S”各一个--您会发现,指南针指针的固定使您无法使用“A”、“B”、“C”和“D...”鉴于“ABCD”的“非特殊”性质,编写适用于“ABCDE”或“ABCDEF”的代码成为一个目标。
摘要:通过用重用代码替换复制代码,您的程序将缩小到易于管理的程度。当功能被重用并成为“通用”时,您将可能改进极大极小处理。花费在寻找这些“改进”上的时间将得到充分的回报。
说到点子上,下面是OP发布的一个59行函数的28行版本。它仍然使用每个“罗盘方向”的离散变量。如果将这些变量捆绑到一个数组中,它也可以用1/2的行数来编写。

void remove_token( struct Game *game, int player, char direction ) {
    int *arr, arr_sz;

    //according to the direction, remove the token and add its value to the player's score
    switch( direction ) {
        case 'N':
            arr = game->north;
            arr_sz = --game->north_size;
            break;
        case 'W':
            arr = game->west;
            arr_sz = --game->west_size;
            break;
        case 'E':
            arr = game->east;
            arr_sz = --game->east_size;
            break;
        case 'S':
            arr = game->south;
            arr_sz = --game->south_size;
            break;
    }

    //add the chip value to the player's score
    if (player == 0)
        game->score_a += arr[ arr_sz ];
    else
        game->score_b += arr[ arr_sz ];
}

相关问题