简体   繁体   English

Tic Tac Toe随机AI

[英]Tic Tac Toe Random AI

I am working on building a Tic Tac Toe game with varying AI implementations for a computer opponent for the sake of learning different algorithms and how to implement them. 我正在为计算机对手构建一个具有不同AI实现的Tic Tac Toe游戏,以便学习不同的算法以及如何实现它们。 The first I am trying which should be the easiest is just having the computer choose a random space each time. 我尝试的第一个应该是最简单的只是让计算机每次都选择一个随机空间。

This is working for me to a certain extent, the issue becomes run time. 这在一定程度上对我有用,问题就变成了运行时间。 Every time the aiRandMove() method is called, it takes longer and longer to pick a move to the point where after 5 moves have been made on board (cpu + user combined) the program appears to hang (although this isn't technically the case). 每次调用aiRandMove()方法时,选择移动到船上5次移动(cpu +用户组合)之后,程序似乎挂起的时间越来越长(尽管从技术上讲这不是案件)。

Upon further debugging on my part I realize that this should be expected as the aiRandMove() method is randomly choosing an X and Y coordinate and then the move is tested to see if it is legal. 在我的进一步调试后,我意识到这应该是预期的,因为aiRandMove()方法随机选择X和Y坐标,然后测试移动以查看它是否合法。 As less and less spaces are open, there are fewer and fewer legal moves, thus many more failed attempts by the randomizer to generate a legal move. 随着越来越少的空间被打开,合法移动越来越少,因此随机发生器尝试生成合法移动的失败次数更多。

My questions is, Is there any way I can modify this that would at least reduce the time taken by the function? 我的问题是,有什么方法可以修改这个,至少可以减少函数所用的时间吗? As far as I can tell from googling and just running through the problem myself, I cannot think of a way to optimize this without compromising the "randomness" of the function. 据我所知,谷歌搜索和我自己解决问题,我想不出一种方法来优化这个,而不会影响功能的“随机性”。 I thought about keeping an array of moves the computer attempted but that would not resolve the problem because that would not affect the amount of times rand() generated duplicate numbers. 我考虑过保留计算机尝试的一系列移动,但这不会解决问题,因为这不会影响rand()生成重复数字的次数。 Here is the code for this function which is all that is really relevant to this issue: 以下是此函数的代码,它与此问题非常相关:

//Function which handles the AI making a random move requires a board
//object to test moves legality and player object to make a move with
//both are passed by reference because changes to board state and the player's
//evaluation arrays must be saved
char aiRandMove(Player &ai, Board &game){
   int tryX;
   int tryY; //Variables to store computer's attempted moves
   bool moveMade = false;
   char winner;
   while(!moveMade){
      srand(time(NULL));//Randomizes the seed for rand()
      tryX = rand() % 3;
      tryY = rand() % 3; //coordinates are random numbers between X and Y
      cout << "Trying move " << tryX << ", " << tryY << endl;
      if(game.isLegalMove(tryX, tryY)){
         winner = game.makeMove(tryX, tryY, ai);
         moveMade = true;
      }
   }
   return winner;
}

I have also tried moving the seed function out of the while loop (this was put inside the while to "increase randomness" even though that is something of a logical folly and this has also not improved results. 我也尝试将种子函数移出while循环(这被放在while中以“增加随机性”,即使这是一个逻辑愚蠢的东西,这也没有改善结果。

If all else fails I may just label this method "Easy" and only have random moves until I can tell if I need to block or make the winning move. 如果所有其他方法都失败了,我可能会将此方法标记为“简单”并且只有随机移动,直到我可以判断是否需要阻止或取得胜利。 But perhaps there are other random functions which may assist in this endeavor. 但也许还有其他随机功能可能有助于这一努力。 Any and all thoughts and comments are more than appreciated! 任何和所有的想法和评论都非常感谢!

You need to remove the invalid moves from the equation, such as with the following pseudo-code, using an array to collect valid moves: 您需要从等式中删除无效移动,例如使用以下伪代码,使用数组来收集有效移动:

possibleMoves = []
for each move in allMoves:
    if move is valid:
        add move to possibleMoves
move = possibleMoves[random (possibleMoves.length)]

That removes the possibility that you will call random more than once per attempted move since all possibilities in the array are valid. 这消除了每次尝试移动时多次调用随机数的可能性,因为数组中的所有可能性都是有效的。

Alternatively, you can start the game with all moves in the possibleMoves array and remove each possibility as it's used. 或者,您可以使用possibleMoves数组中的所有移动开始游戏,并删除每个使用它的可能性。

You also need to learn that it's better to seed a random number generator once and then just use the numbers it generates. 您还需要了解,最好为一个随机数生成器播种一次 ,然后只使用它生成的数字。 Seeding it with time(0) every time you try to get a random number will ensure that you get the same number for an entire second. 每次尝试获取一个随机数时,将它与time(0)一起播种将确保您获得相同的数字一整秒。

Given that there is only at most 9 choices, even using your random picking, this would not cause a long delay. 鉴于最多只有9种选择,即使使用随机选择,也不会造成长时间延迟。 What is causing the long delay is calling srand inside the loop. 导致长延迟的原因是在循环内调用srand。 This is causing your program to get the same random numbers for the duration of a second. 这导致您的程序在一秒钟内获得相同的随机数。 The loop is probably being executed millions of times in that second (or would be without the cout call) 循环可能在那一秒内执行了数百万次(或者没有cout调用)

Move the srand call outside of the loop (or better yet, just call it once at the start of your program). srand调用srand循环外部(或者更好,只需在程序开始时调用一次)。

That is not to say you shouldn't look at ways of removing the unavailable moves from the random selection, as it may make a difference for other types of games. 这并不是说你不应该考虑从随机选择中移除不可用移动的方法,因为它可能对其他类型的游戏产生影响。

You could reduce that to very acceptable levels by creating a list of free coordinates and getting a random index in that collection. 您可以通过创建自由坐标列表并在该集合中获取随机索引来将其降低到非常可接受的水平。 Conceptually: 概念:

#include <vector>

struct tictactoe_point
{
    int x, y;
};

vector<tictactoe_point> legal_points;
tictactoe_point point;
for (point.x = 0; point.x < 3; point.x++)
{
    for (point.y = 0; point.y < 3; point.y++)
    {
        if (game.isLegalMove(point.x, point.y))
        {
            legal_points.push_back(point);
        }
    }
}

point = legal_points[rand() % legal_points.size()];
game.makeMove(point.x, point.y, ai);
moveMade = true;

This solution is not optimal, but it's a significant improvement: now, the time it takes to make a move is fully predictable. 这个解决方案不是最优的,但它是一个重大的改进:现在,完成移动所需的时间是完全可预测的。 This algorithm will complete with one single call to rand . 该算法只需一次调用rand

The fact that you call srand each time you pick a number makes the process even slower, but then again, the major problem is that your current solution has to try over and over again. 每次选择一个数字时调用srand这一事实会使进程变得更慢,但是再一次,主要的问题是你当前的解决方案必须反复尝试。 It's not bounded: it may even never complete. 它没有限制:甚至可能永远不会完成。 Even if srand is considerably slow, if you know that it'll run just one time, and not an indefinite number of times, it should be viable (though not optimal either). 即使srand相当慢,如果你知道它只运行一次,而不是无限次,它应该是可行的(虽然也不是最佳的)。

There are many ways to improve on this: 有很多方法可以改进:

  • Keep a list of valid coordinates to play, and remove the coordinates when either the player or the AI plays it. 保留要播放的有效坐标列表,并在播放器或AI播放时删除坐标。 This way you don't have to rebuild the list at every turn. 这样您就不必每次都重建列表。 It won't make a big difference for a tic-tac-toe game, but it would make a big difference if you had a larger board. 对于一个井字游戏来说,它不会有很大的不同,但是如果你有一个更大的棋盘,它会产生很大的不同。
  • Use the standard C++ random function. 使用标准C ++随机函数。 This isn't really an algorithm improvement, but rand() in C is pretty crappy (I know, I know, it's a long video, but this guy really really knows his stuff). 这不是一个算法改进,但C中的rand()非常糟糕 (我知道,这是一个很长的视频,但这个人真的非常了解他的东西)。

The reason why it seems slower every move is because the AI is picking moves that have already been made so it randomly re-picks either another illegal move(Could be recurring) or it picks the correct square. 每次移动似乎变慢的原因是因为AI正在采取已经进行的移动,因此它随机重新选择另一个非法移动(可能重复)或者它选择正确的方块。

To speed this part of your program up you could have a collection(eg linkedlist) that contains the positions, use your random function over this list. 要加快程序的这一部分,您可以拥有一个包含位置的集合(例如,链表),在此列表中使用随机函数。 When a move is picked by you or the AI remove the element from the list. 当您选择移动或AI从列表中删除元素时。

This will remove the recurring process of the AI picking the same squares. 这将消除AI选择相同方块的重复过程。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM