我最近报名参加了cs50的Python人工智能在线课程,第一项项目是使用极小极大算法创建一个井字游戏,我已经尝试过了。但是当我运行从他们网站下载的zip文件中提供的runner.py文件时,出现了错误,例如对于这个语句:i = action[0],提示”‘NoneType’ object is not subscriptable”,你能帮我修正代码或者至少告诉我问题到底是什么吗?谢谢
import mathimport numpy as npyimport sysimport copyX = "X"O = "O"EMPTY = Nonedef initial_state(): """ Returns starting state of the board. """ return [[EMPTY, EMPTY, EMPTY], [EMPTY, EMPTY, EMPTY], [EMPTY, EMPTY, EMPTY]]def player(board): """ Returns player who has the next turn on a board. """ if board == initial_state(): return X numpy_board = npy.array(board) Xno = npy.count_nonzero(numpy_board = X) Ono = npy.count_nonzero(numpy_board = O) if Xno > Ono: return O elif Ono > Xno: return Xdef actions(board): """ Returns set of all possible actions (i, j) available on the board. """ Result = set() for k in range(3): for l in range(3): if board[k][l] == EMPTY: Result.add(board[k][l]) return Resultdef result(board, action): """ Returns the board that results from making move (i, j) on the board. """ i = action[0] j = action[1] if board[i][j] != EMPTY: raise Exception("Invalid Action") new_player = player(board) new_board = copy.deepcopy(board) new_board[i][j] = new_player return new_boarddef winner(board): """ Returns the winner of the game, if there is one. """ for i in range(3): if (board[i][0] == board[i][1] == board[i][2] and board[i][0] != EMPTY): return board[i][0] if (board[0][0] == board[1][1] == board[2][2] or (board[0][2] == board[1][1] == board[2][0]) and board[1][1] != EMPTY): return board[1][1] if (board[0][i] == board[1][i] == board[2][i] and board[0][i] != EMPTY): return board[1][i] else: return Nonedef terminal(board): """ Returns True if game is over, False otherwise. """ if winner(board) != None: return True; numpy_board = npy.array(board) empty_no = npy.count_nonzero(numpy_board == EMPTY) if (empty_no == 0): return True else: return Falsedef utility(board): """ Returns 1 if X has won the game, -1 if O has won, 0 otherwise. """ win_player = winner(board) if (win_player == X): return 1 elif (win_player == O): return -1 else: return 0def minimax(board): """ Returns the optimal action for the current player on the board. """ if terminal(board): return None currentPlayer = player(board) if currentPlayer == X: return max_value(board)[1] else: return min_value(board)[1]def max_value(board): if terminal(board): return (utility(board), None) value = -sys.maxsize-1 optimalAction = None for action in actions(board): possibleResult = min_value(result(board, action)) if possibleResult[0] > value: value = possibleResult[0] optimalAction = action if value == 1: break return (value, optimalAction)def min_value(board): if terminal(board): return (utility(board), None) value = sys.maxsize optimalAction = None for action in actions(board): possibleResult = max_value(result(board, action)) if possibleResult[0] < value: value = possibleResult[0] optimalAction = action if value == -1: break return (value, optimalAction)
回答:
存在几个问题:
- 在
Xno = npy.count_nonzero(numpy_board = X)
中存在语法错误。你漏了一个等号,应该是==
。下一行类似的语句也有同样的错误 elif Ono > Xno:
中的条件永远不会为真(仔细想想)。此外,这个条件留下了可能不进入任何一个块的情况,返回None
。要么是X的回合,要么不是。如果不是X的回合,那么总是O的回合。你不需要第二个测试。所以将这一行改为else:
Result.add(board[k][l])
添加的不是坐标对,而是方格的内容。这不是你想要的。你想要存储坐标。所以这应该改为Result.add((k, l))
。注意:不要对这样的名称使用Pascal大小写,而应使用驼峰命名法。- 在
winner
函数中,for
循环总是会在第一次迭代时退出。它从不执行其他迭代。在第一次迭代中,你无法知道足够的信息来返回None
。所以删除那个else: return None
:在这种情况下,循环应该继续进行。注意:对角线的测试最好移到循环之外,因为它没有必要重复三次测试。它不依赖于循环变量。
如果进行这些修正,应该可以正常工作。
其他一些建议:
- 如果你打算从列表中创建一个numpy数组,那么为什么不从一开始就只创建numpy数组,并只使用它而不使用列表?在
player
和terminal
中每次进行转换会影响性能。 - 此外,计算X的数量然后计算O的数量需要两次迭代,而你可以在一次扫描中计算空单元格的数量,并从中推断出非空单元格的数量。更快的方法是只维护一个计数器,在玩一招时增加它,在回溯时减少它。
- 上述提到的计数器可以用来快速确定当前玩家。如果玩的回合数是偶数,那么是X的回合,否则是O的回合。
deepcopy
有性能成本。考虑使用相同的列表/数组而不复制它。你只需要在递归调用后添加一个“撤销”操作。- 不要重新创建可能的移动集,也考虑增量维护一个集合:在玩一招时从集合中移除一个动作,在回溯时将其放回。这将提高性能。
-
不要使用这种模式:
if (empty_no == 0): return Trueelse: return False
首先,括号不是必需的,但更重要的是:当你已经有一个布尔表达式(
empty_no == 0
)时,直接返回它。不要使用这种if..else
结构:return empty_no == 0
- 极小极大算法只返回-1、0或1的值,这意味着它不偏好快速胜利而不是慢速胜利。这可能导致令人惊讶的移动,直接胜利不会被执行。为了改进这一点,考虑使用更动态的值。一个想法是更改
utility
函数,使X获胜时返回空单元格的数量加1。对于O,它将是该值的负数。这样可以偏好快速胜利。