You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Minimax算法实现异常求助:最优代币收集游戏优化

问题描述

我正在完成一项作业,需要开发一款由电脑自动模拟对战的逻辑游戏,目标是收集总价值最高的代币。程序输入格式如下(输入以'END'结束,后续计划移除该标识):

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

字母代表方向(东E、西W、北N、南S),方向后的数字为对应代币的价值,玩家仅能从各方向的末端取代币,最终收集代币总价值高的玩家获胜。

我希望游戏以最优策略自动运行,因此选用Minimax算法,但完全不清楚如何正确实现。目前我的代码虽能运行,但无法达到最优效果:例如上述输入的最优结果应为A/B:15/16,而实际运行结果为A/B:14/17。

附上我的C语言代码,恳请帮忙修正算法实现,或提供优化建议。

#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;
}
核心问题分析与修正方案

你的Minimax实现存在几个关键逻辑错误,导致无法计算出最优策略:

1. 函数返回值逻辑完全错误

当前minimax函数返回方向字符,但递归调用时却将该字符当作整数得分使用,这完全违背Minimax的核心逻辑——Minimax应返回当前状态下的最优得分差值(比如玩家A得分减去玩家B得分),而非直接返回方向。

修正方案:

重构minimax函数,让它返回当前状态的最优得分值,通过指针参数输出最优方向。修改后的函数签名:

int minimax(struct Game *game, int player, int alpha, int beta, char *best_direction)

2. 终止状态评估错误

当游戏结束(所有代币取完)时,应返回当前玩家的得分优势:对于玩家A,返回game->score_a - game->score_b;对于玩家B,同样基于该差值选择最小化结果(因为B的目标是让A的优势最小)。而非返回无意义的'X'字符。

3. 搜索深度设置错误

你当前设置depth = 1,仅搜索一步,完全浪费了Minimax的深度搜索能力。对于示例输入,总共有10个代币,需将深度设置为总代币数,或直接移除深度限制(游戏状态空间不大,无需剪枝深度)。

4. 最优方向选择逻辑错误

当前代码将递归返回的方向字符当作得分比较,完全逻辑混乱。正确逻辑:

  • 玩家A(最大化玩家):遍历所有可能移动,计算每个移动后的得分差值,选择差值最大的方向。
  • 玩家B(最小化玩家):遍历所有可能移动,计算每个移动后的得分差值,选择差值最小的方向。
修正后的关键代码示例

以下是修正后的minimax核心实现:

int minimax(struct Game *game, int player, int alpha, int beta, char *best_direction) {
    // 终止条件:所有代币被取完
    if (game->north_size == 0 && game->west_size == 0 && game->east_size == 0 && game->south_size == 0) {
        return game->score_a - game->score_b; // 返回A与B的得分差
    }

    int best_val;
    char dir = 'X';

    if (player == 0) { // 玩家A:最大化得分差
        best_val = INT_MIN;
        // 尝试北方向
        if (game->north_size > 0) {
            struct Game copy = *game;
            remove_token(&copy, 0, 'N');
相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.08 10:30:52