我尝试为Tic-Tac-Toe游戏制作一个单一的玩家模式.我不知道这是否是正确的做法,并希望得到第二种意见。
// keeps track of all the valid positions.
bool validPos[10] = {false, true, true, true, true, true, true, true, true, true};
char user = 'O', computer = 'X';
int main(void)
{
// A two-dimensional array depicting the game.
char game[3][3] = {'1', '2', '3', '4', '5', '6', '7', '8', '9'};
char choice;
// Asks user for a valid input.
do
{
printf("X or O ?\n");
scanf(" %c", &choice);
}while(choice != 'X' && choice != 'O' && choice != 'x' && choice != 'o');
// assigns X or O to the player.
if(choice == 'x' || choice == 'X')
{
user = 'X';
computer = 'O';
}
display(game);
printf("You - %c\n", user);
printf("Computer - %c\n", computer);
startGame(game, user);
}void startGame(char game[3][3], const char turn)
{
int i, j;
int pos;
srand((unsigned)time(NULL));
if(turn == user)
{
// asks user for a valid input.
do
{
printf("Your turn : ");
scanf("%d", &pos);
}while((pos > 9 || pos < 1) || !validPos[pos]);
}
else
{
do
{
// calculates a good and valid move for the computer.
pos = rand() % 10;
}while((pos > 9 || pos < 1) || !validPos[pos]);
pos = calcMove(game, pos);
}
// makes the position chosen invalid.
validPos[pos] = false;
// Assigns 'X' or 'O' to the chosen position.
for( i = 0; i < 3; i++)
for( j = 0; j < 3; j++)
if(game[i][j] - '0' == pos)
game[i][j] = turn;
// prints the game onto the screen.
display(game);
// checks if the player or computer has won or not.
if(gameStatus(game))
{
if(turn == user)
printf("You Win !\n");
else
printf("Computer Wins !\n");
return;
}
// checks if there are any further moves possible.
// also acts as the base case for the game to end.
if(isComplete(game))
{
printf("Game Over!\nNo More Possible Moves\n");
return;
}
// recursive call to continue the game.
if(turn == user)
startGame(game, computer);
if(turn == computer)
startGame(game, user);
}函数calcMove这是我不确定我的方法计算一个好的移动计算机,因为我觉得这是多余的。
int calcMove(char game[][3], int pos)
{
int i, j;
int move = 0;
// If there is any move along a row that is a winning move for the computer
// then take it or blocks the winning move of the user along a row.
for(i = 0; i < 3; i++)
{
if(game[i][0] == game[i][1] && validPos[game[i][2] - '0'])
{
move = game[i][2] - '0';
if(game[i][0] == computer)
return move;
}
if(game[i][0] == game[i][2] && validPos[game[i][1] - '0'])
{
move = game[i][1] - '0';
if(game[i][0] == computer)
return move;
}
if(game[i][1] == game[i][2] && validPos[game[i][0] - '0'])
{
move = game[i][0] - '0';
if(game[i][1] == computer)
return move;
}
}
// If there is any move along a coloumn that is a winning move for the computer
// then take it or blocks the winning move of the user along a coloumn.
for(j = 0; j < 3; j++)
{
if(game[0][j] == game[1][j] && validPos[game[2][j] - '0'])
{
move = game[2][j] - '0';
if(game[0][j] == computer)
return move;
}
if(game[1][j] == game[2][j] && validPos[game[0][j] - '0'])
{
move = game[0][j] - '0';
if(game[1][j] == computer)
return move;
}
if(game[0][j] == game[2][j] && validPos[game[1][j] - '0'])
{
move = game[1][j] - '0';
if(game[0][j] == computer)
return move;
}
}
// checks for the winning move along the right diagonal.
// preferance is given to winning move for computer.
if(game[0][0] == game[1][1] && validPos[game[2][2] - '0'])
{
move = game[2][2] - '0';
if(game[0][0] == computer)
return move;
}
if(game[0][0] == game[2][2] && validPos[game[1][1] - '0'])
{
move = game[1][1] - '0';
if(game[0][0] == computer)
return move;
}
if(game[1][1] == game[2][2] && validPos[game[0][0] - '0'])
{
move = game[0][0] - '0';
if(game[1][1] == computer)
return move;
}
// checks for the winning move along the left diagonal.
// preferance is given to winning move for computer.
if(game[0][2] == game[1][1] && validPos[game[2][0] - '0'])
{
move = game[2][0] - '0';
if(game[0][2] == computer)
return move;
}
if(game[0][2] == game[2][0] && validPos[game[1][1] - '0'])
{
move = game[1][1] - '0';
if(game[0][2] == computer)
return move;
}
if(game[1][1] == game[2][0] && validPos[game[0][2] - '0'])
{
move = game[0][2] - '0';
if(game[1][1] == computer)
return move;
}
// if there was any such move in which either the player or
// the computer would win, then return it.
if(move != 0)
return move;
// if there was no such move, then select any of the corners
// or the center randomly.
switch(rand() % 4)
{
case 1 : if(validPos[7] && validPos[3])
return 7;
case 2 : if(validPos[3] && validPos[7])
return 3;
case 3 : if(validPos[1] && validPos[9])
return 1;
case 4 : if(validPos[5])
return 5;
default : return pos;
}
}我还没有包括函数disply()、isComplete()和gameStatus()的代码片段,因为我认为它们很简单,可能没有什么可回顾的。
发布于 2017-02-06 16:53:59
以下是一些可以帮助您改进代码的事情。
的初始化
变量game当前声明并初始化如下:
char game[3][3] = {'1', '2', '3', '4', '5', '6', '7', '8', '9'};但这些并不完全吻合。相反,我建议将这一行改为:
char game[3][3] = {{'1', '2', '3'}, {'4', '5', '6'}, {'7', '8', '9'}};中的全局变量
拥有依赖于全局变量的例程使得理解逻辑变得更加困难,并引入了许多错误的机会。在实用的地方消除全局变量总是一个好主意,无论是为桌面机器编程还是为嵌入式系统编程。在这段代码中,我建议所有游戏状态变量,包括板、当前回合和使用的令牌都可以包装在一个结构中,然后可以将指向该结构的指针传递给每个相关函数。这使得它更容易理解和维护。
考虑到将validPos数组传递给每个函数,实际上并不需要game数组。一个位置在这个游戏意义上是“有效”的,如果它还没有被占据的话,那么代替这个:
if(game[i][0] == game[i][1] && validPos[game[i][2] - '0'])你可以写这个:
if(game[i][0] == game[i][1] && isdigit(game[i][2]))该程序目前在每次转弯前都调用srand。这是不必要的,也是不可取的。相反,只需在程序开始时调用它一次,然后继续使用rand()获取随机数。
rand()如何工作rand()函数返回一个介于0和RAND_MAX之间的数字。为了简单起见,假设RAND_MAX是15 (实际上要大得多)。一个随机数生成器(或psuedo-随机数生成器)应该以相同的频率生成每个可能的数字,因此我们会有0发生的次数是1/16次,1次发生的次数是1/16次,等等。到目前为止还好,但是让我们看看当我们使用这样的构造时会发生什么:
pos = rand() % 10;这是概率表:
rand pos probability
0 0 1/16
1 1 1/16
2 2 1/16
3 3 1/16
4 4 1/16
5 5 1/16
6 6 1/16
7 7 1/16
8 8 1/16
9 9 1/16
10 0 1/16
11 1 1/16
12 2 1/16
13 3 1/16
14 4 1/16
15 5 1/16因此,产生pos的概率是:
pos probability
0 2/16
1 2/16
2 2/16
3 2/16
4 2/16
5 2/16
6 1/16
7 1/16
8 1/16
9 1/16由于10没有均匀地划分为RAND_MAX,所以在数据范围的低端比在范围末尾的数字更有可能被选择。对于这样一个简单的游戏,它可能并不重要,但这是一个重要的概念,了解。为了使您有一个均匀的分布,您可以这样做:
int pos = rand() / (RAND_MAX / N + 1);其中,N被定义为范围的上限(本例中为10)。
从实际的角度来看,通常将输入、输出和计算分离为这样的程序是很好的做法。通过将它们放在单独的函数中,它将平台的特定I/O (该平台或操作系统可能是唯一的)与程序的逻辑(不依赖于底层操作系统)隔离开来。用户提示以及用户移动的获取和验证可以从主游戏逻辑中分离出来,保持I/O和游戏逻辑之间的清晰分离。
函数isComplete并不坏,因为很明显,返回的true值意味着游戏已经完成。(虽然我可能更喜欢isFull来区分那个状态和一个玩家获胜的状态。)然而,gameStatus函数并没有这么简单的解释。因此,我可能会将该函数命名为isWon。
实际上并不需要对startGame的递归调用。在这种情况下,没有太大的危害,但在有更多的调用的情况下,它可能会炸毁堆栈,并导致您的程序崩溃。更好的方法是隔离游戏逻辑,这样我们就可以将函数命名为startGame,而不是playGame,它看起来可能如下所示:
bool playGame (void) {
char game[3][3] = {{'1', '2', '3'}, {'4', '5', '6'}, {'7', '8', '9'}};
bool humanTurn = true;
while (!isWon(game) && !isFull(game))
{
display(game);
if (humanTurn) {
humanMove(game);
} else {
computerMove(game);
}
humanTurn = !humanTurn;
}
return showResults(game, !humanTurn);
}现在很清楚游戏是如何构造的,如果这个应用程序是基于文本的还是基于GUI的,那么基本的游戏逻辑就可以被使用,没有改变。
scanf的调用可能失败。您必须检查返回值以使其没有,否则当给定格式错误的输入时,您的程序可能会崩溃(或更糟)。严格的错误处理是大多数工作软件和无错误软件之间的区别。你应该为后者而奋斗。
重构超长calcMove()函数的一种方法是为每一个可能的移动计算一个分数,然后选择最高的值。例如,您可以有一个3×3的整数网格,初始化为0。然后为您在注释中已经确定的每个条件添加点数:
1. the move is valid and would win
2. the move is valid and would block a user win
3. the move is valid and is a corner or the middle
4. the move is valid and is an edge当然,第一类比最后一类更有价值,所以你应该适当地加权这些点。
https://codereview.stackexchange.com/questions/154568
复制相似问题