动态规划(dp)+广度优先搜索(BFS)预测井字棋(Tic-Tac-Toe)游戏输赢

动态规划(dp)+广度优先搜索(BFS)预测井字棋(Tic-Tac-Toe)游戏输赢

问题描述

玩家X先下棋。两个玩家都会选择最优的位置下棋。
输入为三行字符,每行三个,其中‘X’代表玩家X的棋子,‘O’为玩家O的棋子,‘#’为棋盘上的空格。
输出为预测结果,如果玩家X胜利,输出‘X’,玩家O胜利,输出‘O’,平局输出‘Tie’
(类似于CCF CSP 201803-4 棋局评估)

示例输入:
#OX
X##
#OX

示例输出:
O

解决思路

1

由于每局都是X先下棋,那么给定任意一个棋盘状态,通过棋盘上剩余的空格数,可以知道现在轮到谁下了,

def getTurn(board): {
    
     
turntable=['X','O'] 
return turntable[(board.count('#')+1)%2] 
}

可以知道,例子中的棋局状态现在该O下棋。O有4种选择(对应棋盘上4个#),即(0,0)、(1,1)、(1,2)、(2,0),分别对应4个新的状态(当前状态的子状态)。即:

【1】对于每一个状态,其子状态是棋局中空位的个数。

而每一个状态输赢可以通过判断每行每列和对角线是否一样:

def checkWin(board): {
    
    
winTable = ['XXX','OOO']
for each line in [board.rows, board.columns, board.diags]{
    
    
if line in winTable {
    
    return line[0]}
return None
}}

显然,当O选择在(1,1)下棋,O可以立刻赢,而其他位置下均不能立刻赢,由于玩家均会做出最优的选择,因此,O会选择下在该处并赢得比赛。因此有论断:

【2】对于一个棋局的状态,如果当前该玩家A下棋,而且下棋的多种选择中(多个子状态中)有任意一种可以赢得比赛,那么当前状态下玩家A获胜

2

再看另一个例子:
XOX
#X#
O#O

现在轮到X下棋,X有三种选择,即(1,0)、(1,2)、(2,1)。如果X下在(1,0)、(1,2),那么O下在(2,1)处会立即获胜;由于玩家均会做出最优的选择,因此X只能下在(2,1)处来阻挡O胜利。因此有论断:

对于一个棋局状态,如果当前该玩家A下棋,而且下棋的多种选择中(多个子状态中)有任意一种可以不让B赢得比赛,那么在这个状态下玩家B都不能获胜

把这句话反过来看,可以得到:

【3】对于一个棋局状态,如果当前该玩家A下棋,而且下棋的多种选择中(多个子状态中)B均可赢得比赛,那么该状态下玩家B获胜

可以看如下例子:
XX#
OX#
##O

无论O下在何处,X都会赢,因为该状态的所有子状态中X都能赢。

3

将每个状态作为一个节点,该状态的子状态作为节点的子节点,就可以构造一棵树(BFS搜索树)。

# Generate BFS tree
boardTree_dict = {
    
    } # key = node, value = children of a node

boardtree_queue = Queue()
boardtree_queue.enque()

def newBoard(board, position) : {
    
     
new_board = board.copy()
new_board[position.x][position.y] = getTurn(board)
return new_board
}

while(!boardtree_queue.empty())
cur_board = boardtree_queue.deque()
if cur_board not in boardTree_dict :{
    
    boardTree_dict.add(Key=cur_board,Value=[]) }
if checkWin(cur_board)!= None : {
    
    continue} # no need to generate children
if cur_board.count('#')==0: {
    
    continue} # no more place for next move
for each sp in cur_board.sharp_positions : {
    
     # positions of '#'
new_board = newBoard(board, position)
boardTree_dict[cur_board].Value.append(new_board)
boardtree_queue.enque(new_board)
}

然后根据boardTree_dict可以构建完整的BFS树。

4

根据(1)和(2)中的思想来设计动态规划DP算法。 假设(3)中得到的BFS树上的每个节点node包含属性:
node.board 棋局局面
node.winner 棋局谁赢了,初始值为checkWin(node.board)
node.turn 该谁下棋,值为getTurn(node.board)
node.children 当前棋局状态的子状态,即boardTree_dict[node.board]

def dp(node):{
    
    
if node.winner != None: {
    
    return node.winner}
all_flag = True
for each child in node.children:{
    
    
if child.winner == node.turn :{
    
    return node.turn} # 【2】对于一个棋局的状态,如果当前该玩家A下棋,而且下棋的多种选择中(多个子状态中)有任意一种可以赢得比赛,那么当前状态下玩家A获胜
turn_opposite = getOpposite(node.turn)
if child.winner in turn_opposite :{
    
     # 也就是并非都是对手赢
all_flag=False}  
}
if all_flag==True: {
    
    return turn_opposite} # 【3】对于一个棋局状态,如果当前该玩家A下棋,而且下棋的多种选择中(多个子状态中)B均可赢得比赛,那么该状态下玩家B获胜
else: {
    
    return None}
}

dp(board)

其他

(1)(python)每个board作为字典的key时需要转换为纯tuple形式,否则不能被hash
(2)对于没下或者只下一个棋子的棋局,直接输出“Tie”,节省时间
(3)性能:对于20个随机测试用例
#1: Accepted [124 ms, 3 MB]
#2: Accepted [139 ms, 3 MB, 1 points]
#3: Accepted [124 ms, 3 MB, 1 points]
#4: Accepted [482 ms, 9 MB, 1 points]
#5: Accepted [124 ms, 3 MB, 1 points]
#6: Accepted [139 ms, 3 MB, 1 points]
#7: Accepted [140 ms, 3 MB, 1 points]
#8: Accepted [124 ms, 3 MB, 1 points]
#9: Accepted [171 ms, 5 MB, 1 points]
#10: Accepted [140 ms, 3 MB, 1 points]
#11: Accepted [140 ms, 3 MB, 1 points]
#12: Accepted [264 ms, 8 MB, 1 points]
#13: Accepted [467 ms, 10 MB, 1 points]
#14: Accepted [140 ms, 4 MB, 1 points]
#15: Accepted [155 ms, 4 MB, 1 points]
#16: Accepted [139 ms, 3 MB, 1 points]
#17: Accepted [124 ms, 3 MB, 1 points]
#18: Accepted [171 ms, 3 MB, 1 points]
#19: Accepted [139 ms, 3 MB, 1 points]
#20: Accepted [140 ms, 3 MB, 1 points]

猜你喜欢

转载自blog.csdn.net/O_1CxH/article/details/113851504
今日推荐