一游戏类(获取当前棋盘状态,当前落子方)
下面的代码实现了初始化,修改落子后棋盘,以及判断棋局有无结束
#游戏(检测当前棋盘的状态、轮到谁落子,落子后棋盘的状态
class Game():
def _init_(self,board,current_player,previous_state,move):
self.board = board
self.current_player = current_player
self.previous_state = previous_state
self.move = move
#落子后棋盘状态变化
def apply_move(self,move):
if move.is_play:
new_board = copy.deepcopy(self.board) #拷贝
new_board.place(self.current_player,move.point) #棋盘放置落下的子
else:
new_board = self.board
return Game(new_board,self.current_player.other,self,move)
@classmethod
def new_game(cls,board_size):
if(isinstance(board_size,int)):
board_size = (board_size,board_size)
broad = Board(*board_size)
return Game(board,Player.black,None,None)
#游戏是否结束
def is_over(self):
if self.move is None:
return False
if self.move.is_resign:
return True
if self.previous_state.move is None:
return False
#双方pass,游戏结束
return self.move.is_pass and self.previous_state.move.is_pass
二 玩家落子非法性检测
分析:上一篇中下列落子的非法性还没有检测
1.自填
2.打劫时不找劫财就提回
3.在对方不是剩一口气的情况下直接下到别人的真眼中
其实 1和3可以放在一起来检测,因为如果一方下棋使得对方的棋的气为0的话,对方棋子就要从棋盘上拿掉,那么自己的棋气就不会是0,也就不属于自填了,所以如果满足3的话,那你就没有使对方的气为0,那你直接填入对方眼中,相当于落了一个气为0的子,也就是自填了
解决:
1.针对1和3这种情况(判断在使自己气为0的时候有无把对方提掉)
由于我们在前面Board类的放置棋子place方法中已经考虑了放置棋子后产生的效果(可能连接了棋子块,可能使对方 棋子块气变少甚至使其气为0从棋盘中拿掉),那么我们可以先把棋子放在棋盘上,得到放置后的新棋子块(此时肯定不存在对方无气的棋子块),再来看这个新棋子块的气,如果还是0,说明就是自填了,如果不是0,说明不是自填
#判断当前落子是不是自填
#注意如果是把对方棋子吃掉的填子是允许的
def is_move_killself(self,player,move):
if not move.is_play:
return False
new_board = copy.deepcopy(self.board)
#先落子,在棋盘类里已经写明如果落下后使对方气没了,那会拿掉对方的棋子,这样保证不是自填
new_board.place(player,move.point)
new_block = new_board.getBlock(move.point)
#如果新的棋子块的气为0,则是自填的
return new_block.num_liberties()==0
2.针对2这种情况(下面的效率比较低,后面可以改进)
当一方提劫后,另一方不找劫财直接去提劫,我们将看到当前游戏的界面与对方未提劫前的界面是一样的,而围棋是禁止同型情况出现的,因此我们避免这种情况出现的方法就是记录游戏的状态,然后每次落子都去判断落子后的状态有无出现过,如果出现过就是非法的
预防打劫不找劫财,即双方落子后的棋盘状态不能和落子前一样
@property
def situation(self):
return (self.current_player,self.board)
#判断落子后局面有无出现过
def is_exist_situation(self,player,move):
if self.move is None:
return False
new_board = copy.deepcopy(self.board)
new_board.place(player,move.point)
next_situation = (player.other,new_board)
#之前游戏的状态
past_state = self.previous_state
#判断当前的状态有无出现过,出现过是非法的
while past_state is not None:
if past_state.situation == next_situation:
return True
return False
总的判断落子是否有效
#总结,判断落子是否有效
def is_move_valid(self,move):
if(self.is_over()):#棋局结束
return False
if move.is_pass or move.is_resign:#有无pass和投降
return True
return (self.board.getColor(move.point) is None and not self.is_move_killself(self.current_player,move) and not self.is_exist_situation(self.current_player,move))
三 判断有无填真眼(主要为了之后AI不要这样走)
分析: 在围棋中如果一个未被占据的交叉点上下左右四个方向的相邻点(边是三个方向,角上是两个方向)都是同样颜色的话,那这个交叉点就是一个眼,那眼分为真眼和假眼,假眼就是说你这眼可以被破坏掉,而真眼就是除非这个棋子块的气只剩一口,对方可以吃掉以外,对方无论如何无法落在这个地方,那真眼有三种情况:
1.交叉点是在棋盘中央,这个时候如果交叉点的对角方向的四个点都是同色的当然是真眼,其实拿掉一个点,也是一个真眼(对方无法破坏),因此我们只需判断对角上的同色棋子是否达到了3即可
2.交叉点在棋盘的边上,这时我们会发现对角会有两个方向在棋盘外,同时一个相邻点也在棋盘外,这种情况下,我们发现在棋盘内的对角方向的相邻点一旦有一个点被对方占据,那就是假眼了,因此要保证这两个方向的相邻点是同色的
3.交叉点在角上,相邻点只有两个在棋盘内,并且只有一个对角方向相邻点在棋盘内,而我们要保证这个对角方向的相邻点是同色的才可以是真眼
判断2和3这两种情况时,原博主用了一个很巧妙的方法,就是无论是2还是3,只要保证对角方向同色交叉点个数加棋盘外交叉点个数达到4即可,说明对角方向上没有其他颜色的棋子
def is_point_in_eye(board,point,color):
if board.getColor(point) is not None:#当前点已被占
return False
#判断该点的邻接点是否为己方棋子,一个眼的邻接点肯定是一样颜色,当这个点在中间,对角线中至少三个是一样颜色,就是真眼,边角区域有效对角线必须全是
for neighbor in point.neighbor:
if board.is_on_grid(neighbor):#邻接点在棋盘内
neighbor_color = board.get(neighbor)
if(neighbor !=color):
return False
#判断对角线情况,看是否是真眼,真眼不要去填
#对角线是己方棋子个数
me_corners = 0
off_board_corners = 0#在棋盘外的对角线点
corners = [
Point(point.row - 1,point.col - 1),
Point(point.row - 1, point.col + 1),
Point(point.row + 1, point.col - 1),
Point(point.row + 1, point.col + 1),
]
for corner in corners:
if board.is_on_grid(point):
corner_color = board.getColor(corner)
if corner_color == color:
me_corners += 1
else:
off_board_corners += 1
#有在棋盘外的对角点,那么要保证对角上已方棋子数和棋盘外棋子数加起来为4
if off_board_corners>0:
return off_board_corners + me_corners == 4
#所在点在中央,只要对角点是三个一样颜色的棋子,就是真眼
else:
return me_corners >= 3
自此,搭建AI所需的基础类就差不多了,之后就要去制作一个下围棋的AI了,当然还不是带神经网络的