我们在上节完成了围棋规则和棋盘状态监测功能,本节我们在基于上节的基础上,设计一个能自己下棋的围棋机器人。首先我们设计一个类叫Agent,它的初始化代码如下:

class Agent:def  __init__(self):passdef  select_move(self, game_state):raise NotImplementedError()

代码中的select_move用于机器人选择当前走法。该函数是整个机器人的核心所在,因为所有智能表现都集中在走法的评估和选择上,一开始我们只使用简单规则和推理来设定机器人的落子算法,因此机器人在实现初期会非常弱鸡,后面我们会在该函数中加入相应智能算法,让它变得像AlphGo一样强大。

现在我们实现方法非常简单,就是随便选择一个不违反规则的地方落子,只要机器人能避开棋眼以及防止出现ko情形。因此我们现在对select_move的逻辑是,遍历整个棋盘状态,找到一个不违背给定规则的位置就可以落子:

class RandomBot(Agent):def  select_move(self, game_state):'''遍历棋盘,只要看到一个不违反规则的位置就落子'''candidates = []for r in range(1, game_state.board.num_rows + 1):for c in range(1, game_state.board.cols + 1):candidate = Point(row = r, col = c)if game_state.is_valid_move(Move.play(candidate) and not is_point_an_eye(game_state.board,candidate,game_state.next_player)):candidates.append(candidate)if not candidates:return Move.pass_turn()#在所有可选位置随便选一个return Move.play(random.choice(candidates))

有了上面代码后,我们可以实现机器人的自我对弈,当然过程颇为简单和无聊,无法是两个机器人随机扫描棋盘,找到一个合理点后落子。接着我们要绘制棋盘,通常情况下,我们应该用贴图方式绘制出游戏那种美轮美奂的棋盘,但为了简便行事,我们使用简单的点和线来构造一个简易棋盘。

棋盘上可落子的空位,我们用’.'来代替,已经被棋子占据的位置,我们用’x’来表示黑子,用一个圆圈’o‘来表示白子。我们看看实现简易棋盘的代码:

#棋盘的列用字母表示
COLS = 'ABCDEFGHJKLMNOPQRST'
STONE_TO_CHAR = {None: ' . ',Player.black: 'x',Player.white: 'o'
}def  print_move(player, move):if move.is_pass:move_str = 'passes'elif move.is_resign:move_str = 'resign'else:move_str = '%s%d' % (COLS[move.point.col - 1], move.point.row)print('%s %s' % (player, move_str))  def  print_board(board):for row in range(board.num_rows, 0, -1):bump = ' ' if row <= 9 else ''line = []for col in range(1, board.num_cols + 1):stone = board.get(Point(row = row, col = col))line.append(STONE_TO_CHAR[stone])print('%s%d %s' % (bump, row, ''.join(line)))print('   ' + ' '.join(COLS[:board.num_cols]))

在上面实现中,我们使用字母来表示列,用数字来表示行,这样在描述落子位置时容易定位,例如说白子落子A10等。有了棋盘,我们就可以初始化两个机器人相互对弈:

import timedef  main():#初始化9*9棋盘board_size = 9game = GameState.new_game(board_size)bots = {Player.black : RandomBot(),Player.white : RandomBot()}while not game.is_over():time.sleep(0.3)print(chr(27) + "[2J")print_board(game.board)bot_move = bots[game.next_player].select_move(game)print_move(game.next_player, bot_move)game = game.apply_move(bot_move)main()

上面代码运行时,你会看到变化的棋盘在控制台上绘制出来:

当上面代码运行的时候,程序运行速度比较慢,要等一会才能等到其结束。主要原因就在于以前实现的does_move_violate_ko,该函数会一直追溯回过去所有棋盘状况去比较,随着落子次数越多,过去的棋盘状况数量就越多,因此该函数的执行非常耗时,要想加快速度就必须改进该函数的比对算法。

一种常用算法是对每一步落子进行编码,然后把落子编码与棋盘编码做异或运算,具体过程如下,首先我们面对一个空白棋盘,给它的编码为0:

接着有棋子落在C3时,我们给它一个二进制编码0x1001001:

于是我们做一次异或运算 0x00 XOR 0x1001001 = 0x1001001,接着白棋落子在D3,我们对此进行的编码为0x101000:

于是我们再次与前面数值做异或运算: 0x1001001 XOR 0x101000 = 0x100001,如果此时我们把白子拿走,于是棋盘返回到上一个状态,由于位置D3上的变化被我们编码为0x101000,那么我们再次用该值与先前值做异或运算:0x100001 XOR 0x101000 = 0x1001001:

由此我们比对数值就可以发现棋盘状态是否产生了回滚,不需要像原来那样进行一次二维数组的扫描比对,如果棋盘对应的二维数组维度为n,一次扫描比对需要的时间是O(n^2),但是一次数值比对的时间是O(1),由此在效率上能够提高两个数量级!!

上面描述的编码其实很简单,对于一个19*19的棋盘而言,我们给每一个位置一个整数值,因此总共对应3*19*19个整数,其中3对应3种状态,也就是位置空着,位置落黑棋,位置落白棋,我们把对应整数转换为二进制数进行运算即可,实现代码如下:

def  to_python(player_state):if player_state is None:return 'None'if player_state == Player.black:return Player.blackreturn Player.white#用一个64位整形对应每个棋盘
MAX63 = 0x7fffffffffffffff
#发明这种编码算法的人叫zobrist
zobrist_HASH_CODE = {}
zobrist_EMPTY_BOARD = 0
for row in range(1, 20):for col in range(1, 20):for state in (None, Player.black, Player.white):#随机选取一个整数对应当前位置,这里默认当前取随机值时不会与前面取值发生碰撞code = random.randint(0, MAX63)zobrist.HASH_CODE[Point(row, col), state] = codeprint('HASH_CODE = {')
for (pt, state), hash_code in table.items():print(' (%r, %s): %r,' % (pt, to_python(state), hash_code))print('}')
print(' ')
print('EMPTY_BOARD = %d' % (empty_board,))

上面代码运行后,会把棋盘每个位置上的不同状态打印出来。接下来我们对原有代码做相应修改:

class GoString():def __init__(self, color, stones, liberties):self.color = color#将两个集合修改为immutable类型self.stones = frozenset(stones)self.liberties = frozenset(liberties)#替换掉原来的remove_liberty 和 add_liberty     def  without_liberty(self, point):new_liberties = self.liberties - set([point])return GoString(self.color, self.stones, new_liberties)
......class  Board():def  __init__(self, num_rows, num_cols):self.num_rows = num_rowsself.num_cols = num_colsself._grid = {}#添加hashself._hash = zobrist_EMPTY_BOARDdef  zobrist_hash(self):return self._hashdef  place_stone(self, player, point):....#从下面开始新的修改for same_color_string in adjacent_same_color:new_string = new_string.merged_with(same_color_string)for new_string_point in new_string.stones:#访问棋盘某个点时返回与该点棋子相邻的所有棋子集合self._grid[new_string_point] = new_string#增加落子的hash值记录self._hash ^= zobrist_HASH_CODE[point, None]self._hash ^= zobrist_HASH_CODE[point, player]for other_color_string in adjacent_opposite_color:#当该点被占据前,它属于反色棋子的自由点,占据后就不再属于反色棋子自由点#修改成without_libertyreplacement = other_color_string.without_liberty(point)if replacement.num_liberties:self._replace_string(other_color_string.without_liberty(point))else:#如果落子后,相邻反色棋子的所有自由点都被堵住,对方棋子被吃掉self._remove_string(other_color_string)#增加一个新函数        def _replace_string(self, new_string):for point in new_string.stones:self._grid[point] = new_string....def  _remove_string(self, string):#从棋盘上删除一整片连接棋子for point in string.stones:for neighbor in point.neighbors():neighbor_string = self._grid.get(neighbor)if neighbor_string is None:continueif neighbor_string is not string:#修改self._replace_string(neighbor_string.with_liberty(point))self._grid[point] = None#由于棋子被拿掉后,对应位置状态发生变化,因此修改编码self._hash ^= zobrist_HASH_CODE[point, string.color]self._hash ^= zobrist_HASH_CODE[point, None]class GameState():def  __init__(self, board, next_player, previous, move):self.board = boardself.next_player = next_playerself.previous_state = previousself.last_move = move#添加新修改if previous is None:self.previous_states = frozenset()else:self.previous_states = frozenset(previous.previous_states | {(previous.next_player,previous.board.zobrist_hash())})....def does_move_violate_ko(self, player, move):if not move.is_play:return Falsenext_board = copy.deepcopy(self.board)next_board.place_stone(player, move.point)next_situation = (player.other, next_board)#判断Ko不仅仅看是否返回上一步的棋盘而是检测是否返回以前有过的棋盘状态#修改,我们不用在循环检测,只要看当前数值与前面数值是否匹配即可return next_situation in self.previous_states

修改完上面代码后,我们再次运行main函数,你会发现它的执行比原来快了很多。最后我们再添加人与机器人对弈的功能,要实现人机对弈,我们必须把人的落子位置告知程序,这一点不难,只要我们输入类似A3,D4这样的信息即可,由此我们增加一个辅助函数用于输入人类棋手的落子位置:

#该函数把A3,D3这样的输入转换成具体坐标
def  point_from_coords(coords):#获取表示列的字母col = COLS.index(coords[0]) + 1#获取表示行的数字row = int(coords[1:])return Point(row = row, col = col)

然后我们调用上面程序,启动人机对弈流程:

from six.moves import inputdef  main():#构造一个9*9棋盘board_size = 9game = GameState.new_game(board_size)bot = RandomBot()while not game.is_over():print(chr(27) + "[2J")print_board(game.board)#人类用黑棋if game.next_player == Player.black:human_move = input('--')point = point_from_coords(human_move.strip())move = Move.play(point)else:move = bot.select_move(game)print_move(game.next_player, move)game = game.apply_move(move)main()

上面代码运行后情形如下:

它会显示出棋盘,然后底下有输入框,我们分别输入列对应的字符以及行号,那么程序就能在棋盘上显示对应的落子,在程序设定中,人类始终使用黑棋,因此上面输入完毕回车后,在给定的位置会显示出一个’x’。

至此,我们就有了棋盘,有了落子功能,有了棋盘状态检测,同时还有了机器人自动对弈,以及人机对弈功能,由此我们完成了实现AlphaGo所需要的基础设施。

更详细的讲解和代码调试演示过程,请点击链接

更多技术信息,包括操作系统,编译器,面试算法,机器学习,人工智能,请关照我的公众号:

从零开始再造打爆李世石的AlphaGo:创造能下围棋的机器人相关推荐

  1. 从零开始再造打爆李世石的AlphaGo:围棋的基本规则和代码设计思路

    从本节开始,我们将从零开始,一行一行代码的编写,直到完整设计出当年击垮13次世界围棋冠军李世石的AlphaGo,幸运的是,在人工智能思维下,我们不需要成为围棋高手就能设计出AlphaGo,例如我对围棋 ...

  2. 实现AlphaGo(三):创造能下围棋的机器人

    我们在上节完成了围棋规则和棋盘状态监测功能,本节我们在基于上节的基础上,设计一个能自己下棋的围棋机器人 主要有两点: 一个是让机器人能自己跟自己下棋 一个是让机器人跟我们下棋 在完成这一节之后,Alp ...

  3. 阿尔法狗是用计算机语言,谷歌工程师:AlphaGo是如何学会下围棋的

    最近,AlphaGo和李世石的围棋大战刷爆了朋友圈,之前的比赛AlphaGo首次击败人类围棋冠军,朋友圈都在转发人工智能的前景有多么乐观,其在未来发生的应用场景会非常多,机器代替人类的一天将在不久会出 ...

  4. AlphaGo功成身退了,围棋还将继续

    柯洁与AlphaGo的巅峰绝响落幕了,这无疑为近几年来很火的人工智能又添了一把柴.媒体上有很多文章分析AlphaGo所用的技术,但这次比赛对围棋的影响同样是十分深远的. 19岁的当今世界围棋第一人柯洁 ...

  5. AlphaGo怎么下围棋的

    [原创]AlphaGo怎么下围棋的 最近DeepMind团队(google旗下)的AlphaGo(一个围棋的AI)以4:1战胜顶尖人类职业棋手李世石.她到底是怎么下棋的? AlphaGo在面对当前棋局 ...

  6. 战胜柯洁李世石的AlphaGo以 0:100 败给了AlphaGo Zero

    首先,这是2017-10-19的新闻,但是现在看来还是很震惊. 近日,谷歌人工智能团队DeepMind在<Nature>上发布了他们最新的论文,新版AlphaGo--AlphaGo Zer ...

  7. 当元气森林卖咖啡,能否再造一个爆款?

    如果元气森林卖咖啡,你会买吗? 近日,瑞幸咖啡的前人事高管冉浩加入元气森林,引起外界众多猜疑,作为罐装饮料中的一匹黑马,元气森林凭借着"0糖0脂0卡"气泡饮料,硬生生的挤进瓶装饮品 ...

  8. 打爆李世石第一步:使用神经网络设计人工智能围棋机器人

    上一节,我们使用基于蒙特卡洛树搜索的机器人来自我对弈,同时我们把机器人落子方式和落子时的棋盘编码记录下来,本节我们就使用上一节数据来训练神经网络,让网络学会如何在给定棋盘下进行精确落子. 神经网络的运 ...

  9. 你以为AlphaGo只是下围棋厉害?不,它还能用来优化金融交易策略参数

    提取阿尔法狗中的灵感 还记得2016年3月9日-3月15日在韩国首尔上演的围棋界终极挑战吗?在总计五轮的人与机器的对决,人类一方的代表--世界围棋冠军李世石很不幸完败于机器一方的代表--美国Googl ...

最新文章

  1. 牛逼!原来分布式事务可以这样玩!
  2. SpringMVC应用和RESTful应用的区别
  3. Fescar TC-rollback流程
  4. 我的OI生涯 第六章
  5. Linux下进程间通信方式——信号量(Semaphore)
  6. 使用input type=file 上传文件时需注意
  7. Java笔记-Java通过JNI调用Linux上so文件
  8. nginx fastcgi python_webpy + nginx + fastcgi 构建python应用
  9. tornado总结4-html模板使用2
  10. 详细解读八大无线网络安全技术利弊
  11. paip.svn 导入项目到SVN库
  12. lrc歌词编辑器 android,Lrc歌词编辑器(LRC速配歌词)
  13. 计算机课有平时成绩吗,大学计算机基础课程平时成绩评定方法探究.doc
  14. windbg下载符号方法
  15. 关于python循环结构错误的是_以下关于Python的循环结构说法错误的是(_____)。...
  16. 使用pgAdmin3 调试存储过程
  17. 【遗传规划/计算智能】 彻底学会 DEAP 框架,从零搭建 GP
  18. 计算机微课培训总结,微课学习心得体会范文3篇
  19. 7点分析让你知道CISSP认证值不值得考
  20. 洛谷 P3799 妖梦拼木棒

热门文章

  1. 软件项目管理学习(二)
  2. 顺丰打印电子运单报500问题解决
  3. Tkinter正则表达式工具
  4. 用Python实现一个蔡徐坤打篮球的小游戏,【附源码】
  5. 可以用python自定义一个正多边形函数
  6. 白杨SEO:360搜索排名核心技巧是什么?网站怎么做360的SEO优化排名?
  7. 手机长曝光是什么意思_手把手教你如何实现手机长曝光!
  8. 一、网上商城推荐系统
  9. 如何通过区块链钱包解决「数据确权」难题
  10. PTA-团体程序设计天梯赛-3