我正在做作业。我想创建一个由计算机模拟和玩的游戏。这是一个逻辑游戏,目标是收集最多的具有最高价值的代币。程序开始时要求输入以下格式(“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;
}
1条答案
按热度按时间ttygqcqt1#
这不是一个答案。不过,操作员确实问了一些“提示”。下面是一些:
1.不要为您可以/应该实现的单个函数(
fmax()
)拖入浮点库(math. h),特别是在处理整数值和结果时。下面是两个返回两个整数的最小值或最大值的“无分支”语句:i & j公司
1.您可以安全地从数据文件中删除
END
。fgets()
将找到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的行数来编写。