设为首页收藏本站

Crossin的编程教室

 找回密码
 立即加入
查看: 17658|回复: 4
打印 上一主题 下一主题

用 Python 跟自己下棋

[复制链接]

174

主题

45

好友

11万

积分

管理员

Rank: 9Rank: 9Rank: 9

跳转到指定楼层
楼主
发表于 2016-3-14 00:39:53 |只看该作者 |倒序浏览
用 Python 跟自己下棋

我有一壶酒,足以慰风尘。
自己写 AI,对弈不求人。
(附 Python 版 AlphaGo 开源项目地址)

今天,李世乭终于在与 AlphaGo 的人机大战中扳回一局。但计算机 AI 可以在围棋上战胜人类顶尖棋手的时代已经到来。可以预见,人工智能和机器人将会在更多领域做到比人力更高效、准确、安全。所以未来,掌握编程技能将会显得更加重要。与其现在感叹所谓的机器威胁论,还不如现在动起手来,磨练自己的技能。

再厉害的程序员,也是从“hello world”程序开始写起。再“聪明”的机器,也是从零样本开始“训练”出来的。所以今天就来写一个最简单棋类游戏:Tic Tac Toe,又叫井字棋。

0本篇将实现游戏框架,让你可以和电脑对战,但提升电脑的“智能”会在下一篇中细说。另外,文末会介绍一个 Github 上的 Python 版 AlphaGo 项目。

大致说下井字棋的规则:
棋盘为 3*3 共 9 格,类似汉字“井”;
一方为 o,一方为 x,轮流落子;
任一方先有连成一条线的 3 个棋子(横、竖、斜皆可)则为胜利;
棋盘摆满仍没有一方胜利,则为平局。

我打算在控制台下实现这个游戏,所以我需要用一个格式把棋盘的状态输出出来,设想是这样:

    a   b   c
  |---|---|---|
1 |   | o |   |
  |---|---|---|
2 |   | o | x |
  |---|---|---|
3 | x | o |   |
  |---|---|---|

每走一步,就再次输出新的状态。

而棋盘本身的数据,我用一个 2 维数组来存储:

board = [
  [0, 0, 0],
  [0, 0, 0],
  [0, 0, 0],

0 表示没有子,落子之后,o 为 1,x 为 2。

现在,我需要一个函数,按照设想的格式,把棋盘数据输出到屏幕上。以下是我的实现:

CHESS = [' ', 'o', 'x']

def showBoard():
  print '    a   b   c  '
  for i in range(3):
    print '  |---|---|---|'
    print i+1, '|',
    for j in range(3):
      print '%s |' % CHESS[board[j]],
    print
  print '  |---|---|---|'

为了对应 0、1、2 和空格、o、x 的关系,我用了一个 CHESS 数组。中间的 print 较多,有些乱,但仔细对照前面的设计图看一下应不难理解。

之后考虑游戏的主体玩法部分。大体的思路是:人走一步、显示棋盘、判断是否结束、AI 走一步、显示棋盘、判断是否结束,如此循环。所以大的框架是:

yourturn = True
showBoard()
while not isFinished():
  if yourturn:
    moveMan()
  else:
    moveAI()
  showBoard()
  yourturn = not yourturn;

这里,我用一个变量 yourturn 来记录该哪一方落子,每次走完一步就交换。

isFinished 是一个判断游戏是否结束的函数,如果结束了,就返回 True,游戏主循环退出。最终结果输出,我也打算放在这个函数里。

moveMan 和 moveAI 分别是人和 AI 落子,一个是等待控制台的输入,一个是计算出位置。

接下来要做的,就是完成这 3 个函数。

先来看 moveMan:

ROW = {'1': 0, '2': 1, '3': 2}
COL = {'a': 0, 'b': 1, 'c': 2}

def moveMan():
  print 'Your turn...'
  while True:
    try:
      move = raw_input('choose a position (e.g. a1/c2/b3...):\n')
      pos_row = ROW[move[1]]
      pos_col = COL[move[0]]
      if board[pos_row][pos_col] == 0:
        board[pos_row][pos_col] = 1
        return
    except:
      pass

用 raw_input 等待用户输入。为了方便表示,我们之前的设计就在棋盘上标记了 abc 和 123。ROW 和 COL 两个 dict 是用来将用户输入对应到具体的棋盘坐标上。当判断 board 数组里,用户输入的位置没有棋子时,则指定为 1,并结束函数。while 循环和 try-except 块是为了保证用户的输入是有效的,否则就会重复提示用户输入。

再来看 moveAI:

def moveAI():
  print 'AI\'s turn...'
  while True:
    r = random.randint(0, 2)
    c = random.randint(0, 2)
    if board[r][c] == 0:
      board[r][c] = 2
      return

这个函数的目的是为了将 board 一个位置设置为 2。选取这个位置的过程,则是此游戏 AI 的算法的核心部分。今天先偷个懒,随机生成一个位置,如果为空,就作为落子的位置,并结束函数。下一篇,我们再来完善这个核心。

最后,就是判断胜负的 isFinished:

def isFinished():
  # check row
  if [1, 1, 1] in board:
    print 'You win!'
    return True
  if [2, 2, 2] in board:
    print 'AI wins!'
    return True
  # check col
  for i in range(3):
    if board[0] == board[1] == board[2] == 1:
      print 'You win!'
      return True
    if board[0] == board[1] == board[2] == 2:
      print 'AI wins!'
      return True
  # check diagonal
  if (board[0][0] == board[1][1] == board[2][2] == 1) or (
    board[2][0] == board[1][1] == board[0][2] == 1):
    print 'You win!'
    return True
  if (board[0][0] == board[1][1] == board[2][2] == 2) or (
    board[2][0] == board[1][1] == board[0][2] == 2):
    print 'AI wins!'
    return True
  # check draw game
  draw = True
  for i in range(3):
    if 0 in board:
      draw = False
  if draw:
    print 'Draw game.'
    return True
  return False

稍有点长,主要分为 4 部分:分别是判断横、竖、斜、平局。

横竖斜的胜利部分,就是遍历棋盘去寻找是否有符合条件的情况,有则输出游戏结果,并返回 True。如果都没有,就去判断是否是平局。

判断平局的逻辑是这样:先设定 draw 为 True,如果遇到棋盘上有 0 的位置,则设为 False。否则遍历结束,draw 仍然为 True,就说明已没有空位,游戏以平局结束。

一个井字棋游戏已完成,截取一小段输出结果:

Your turn...
choose a position (e.g. a1/c2/b3...):
b2
  a   b   c
  |---|---|---|
1 | o |   |   |
  |---|---|---|
2 | o | o |   |
  |---|---|---|
3 | x |   | x |
  |---|---|---|
AI's turn...
  a   b   c
  |---|---|---|
1 | o |   |   |
  |---|---|---|
2 | o | o |   |
  |---|---|---|
3 | x | x | x |
  |---|---|---|
AI wins!

当然,现在的这根本还算不上 AI。下一次,我们会让它更“机智”一点。

如果手机上看代码不方便,可移步论坛,在电脑上查看,我也会将完整代码上传。(论坛上的附件需要登录才可下载)


另外,关于前面提到的开源版 AlphaGo 项目。

项目地址:

有人传是 AlphaGo 开源了,但这其实只是 University of Rochester 根据 AlphaGo 的论文做的实现,用了 Python。与真正使用的程序相去甚远。可以去围观,看看代码。对机器学习、神经网络有兴趣的可以深入研究一下,甚至参与项目开发。不过如果你真想在自己的机器上运行项目,那我要提醒你几点:

首先,项目里面用到了 SciPy,而 SciPy 的安装是需要根据不同操作系统编译的,这里面坑不少,至少我是在两个系统上折腾了几小时才安装成功。
另外,项目目前只完成了一个基本框架,和算法中一小部分,完成度很低。虽然也可以训练 AI 走棋,但效果肯定远不如 AlphaGo。
项目里虽然附带了一个 HTML5 的网页围棋接口,但应该还没有对接,所以想跟电脑对战的要失望了。

完整代码:

  1. import random

  2. board = [
  3.     [0, 0, 0],
  4.     [0, 0, 0],
  5.     [0, 0, 0],
  6. ]

  7. ROW = {'1': 0, '2': 1, '3': 2}
  8. COL = {'a': 0, 'b': 1, 'c': 2}

  9. CHESS = [' ', 'o', 'x']

  10. def isFinished():
  11.     # check row
  12.     if [1, 1, 1] in board:
  13.         print 'You win!'
  14.         return True
  15.     if [2, 2, 2] in board:
  16.         print 'AI wins!'
  17.         return True
  18.     # check col
  19.     for i in range(3):
  20.         if board[0][i] == board[1][i] == board[2][i] == 1:
  21.             print 'You win!'
  22.             return True
  23.         if board[0][i] == board[1][i] == board[2][i] == 2:
  24.             print 'AI wins!'
  25.             return True
  26.     # check diagonal
  27.     if (board[0][0] == board[1][1] == board[2][2] == 1) or (
  28.         board[2][0] == board[1][1] == board[0][2] == 1):
  29.         print 'You win!'
  30.         return True
  31.     if (board[0][0] == board[1][1] == board[2][2] == 2) or (
  32.         board[2][0] == board[1][1] == board[0][2] == 2):
  33.         print 'AI wins!'
  34.         return True
  35.     # check draw game
  36.     draw = True
  37.     for i in range(3):
  38.         if 0 in board[i]:
  39.             draw = False
  40.     if draw:
  41.         print 'Draw game.'
  42.         return True
  43.     return False

  44. def moveAI():
  45.     print 'AI\'s turn...'
  46.     while True:
  47.         r = random.randint(0, 2)
  48.         c = random.randint(0, 2)
  49.         if board[r][c] == 0:
  50.             board[r][c] = 2
  51.             return

  52. def moveMan():
  53.     print 'Your turn...'
  54.     while True:
  55.         try:
  56.             move = raw_input('choose a position (e.g. a1/c2/b3...):\n')
  57.             pos_row = ROW[move[1]]
  58.             pos_col = COL[move[0]]
  59.             if board[pos_row][pos_col] == 0:
  60.                 board[pos_row][pos_col] = 1
  61.                 return
  62.         except:
  63.             pass

  64. def showBoard():
  65.     print '    a   b   c  '
  66.     for i in range(3):
  67.         print '  |---|---|---|'
  68.         print i+1, '|',
  69.         for j in range(3):
  70.             print '%s |' % CHESS[board[i][j]],
  71.         print
  72.     print '  |---|---|---|'

  73. yourturn = True
  74. showBoard()
  75. while not isFinished():
  76.     if yourturn:
  77.         moveMan()
  78.     else:
  79.         moveAI()
  80.     showBoard()
  81.     yourturn = not yourturn;


复制代码

代码文件下载:



tictactoe.py

2.05 KB, 下载次数: 14

#==== Crossin的编程教室 ====#
微信ID:crossincode
网站:http://crossincode.com
回复

使用道具 举报

0

主题

0

好友

34

积分

新手上路

Rank: 1

沙发
发表于 2016-3-16 10:07:42 |只看该作者
哇哦,终于赶上进度了

请教crossin老师一个coding的细节:

比如要在给一大段代码前加上if判断语句,我通常的做法是先写上if语句,然后把后面的所有代码逐行缩退4格。但是这样在代码很长的时候太痛苦了,不知道有没有能整体缩退的办法?

回复

使用道具 举报

174

主题

45

好友

11万

积分

管理员

Rank: 9Rank: 9Rank: 9

板凳
发表于 2016-3-16 10:40:00 |只看该作者
mike90326 发表于 2016-3-16 10:07
哇哦,终于赶上进度了

请教crossin老师一个coding的细节:

一般编辑器都会有支持类似的功能吧。大多数是全部选中了按tab键,但要注意有的tab是制表符,有的tab是4个空格,不能混用。这个要看编辑器设置了。退回来很多是shift+tab
#==== Crossin的编程教室 ====#
微信ID:crossincode
网站:http://crossincode.com
回复

使用道具 举报

0

主题

0

好友

34

积分

新手上路

Rank: 1

地板
发表于 2016-3-16 10:59:12 |只看该作者
crossin先生 发表于 2016-3-16 10:40
一般编辑器都会有支持类似的功能吧。大多数是全部选中了按tab键,但要注意有的tab是制表符,有的tab是4个 ...

我用的是pycharm,试了一下tab,果然好用!感谢!
回复

使用道具 举报

0

主题

0

好友

4

积分

新手上路

Rank: 1

5#
发表于 2017-5-4 16:06:26 |只看该作者
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即加入

QQ|手机版|Archiver|Crossin的编程教室 ( 苏ICP备15063769号  

GMT+8, 2024-11-23 03:41 , Processed in 0.025111 second(s), 24 queries .

Powered by Discuz! X2.5

© 2001-2012 Comsenz Inc.

回顶部