用 Python 跟自己下棋

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

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

Tic Tac Toe,又叫井字棋。

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

大致说下井字棋的规则:

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

我打算在控制台下实现这个游戏,所以我需要用一个格式把棋盘的状态输出出来,设想是这样(发到手机上可能有点走形):

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

abc 和 123 是为了更方便地标记棋盘上的位置。每走一步,就再次输出新的状态。

而棋盘本身的数据,我用一个 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[i][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 等待用户输入。这里约定了表示位置的输入格式。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][i] == board[1][i] == board[2][i] == 1: print 'You win!' return True if board[0][i] == board[1][i] == board[2][i] == 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[i]: 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 项目。

项目地址:

https://github.com/Rochester-NRT/AlphaGo

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

首先,项目里面用到了 SciPy,而 SciPy 的安装是需要根据不同操作系统编译的,这里面坑不少,至少我是在两个系统上折腾了几小时才安装成功。

另外,项目目前只完成了一个基本框架,和算法中一小部分,完成度很低。虽然也可以训练 AI 走棋,但效果肯定远不如 AlphaGo。

项目里虽然附带了一个 HTML5 的网页围棋接口,但应该还没有对接,所以想跟电脑对战的要失望了。

本文分享自微信公众号 - Crossin的编程教室(crossincode)

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2016-03-14

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏架构说

leetcode打家劫舍问题

https://leetcode-cn.com/problems/house-robber/description/

23320
来自专栏ACM算法日常

为什么vjudge上他人公开的代码要以图片形式显示?

vjudge用图片来显示代码,应该是为了避免抄袭。在较低水平的oier中,已经有交别人的代码来通过题目的风气。举个例子,洛谷上抄袭代码情况极其严重,而u...

13520
来自专栏逍遥剑客的游戏开发

PhysX学习笔记(3): 动力学(2) Actor

23920
来自专栏机器学习AI算法工程

Python NLTK自然语言处理:词干、词形与MaxMatch算法

CSDN:白马负金羁 自然语言处理是计算机科学领域与人工智能领域中的一个重要方向。自然语言工具箱(NLTK,Natural Language Toolkit)...

57150
来自专栏落影的专栏

OpenGL ES实践教程(九)OpenGL与视频混合

前言 前面的实践教程: OpenGL ES实践教程1-Demo01-AVPlayer OpenGL ES实践教程2-Demo02-摄像头采集数据和渲染 O...

65250
来自专栏程序猿

【学生管理系统C语言】没有用数据库,正在学习C语言的看看吧

#include<stdio.h> #include<string.h> #include<stdlib.h> #include<math.h> # defin...

576110
来自专栏CDA数据分析师

为什么说 Python 是数据科学的发动机(二)工具篇(附视频中字)

毋庸置疑,Python是用于数据分析的最佳编程语言,因为它的库在存储、操作和获取数据方面有出众的能力。 在PyData Seattle 2017中,Jake V...

256100
来自专栏前端布道

JavaScript设计模式与开发实践 - 策略模式

引言 本文摘自《JavaScript设计模式与开发实践》 在现实中,很多时候也有多种途径到达同一个目的地。比如我们要去某个地方旅游,可以根据具体的实际情况来选择...

39680
来自专栏张善友的专栏

Ring Buffer 有什么特别?

原文地址: http://mechanitis.blogspot.com/2011/06/dissecting-disruptor-whats-so-speci...

30870
来自专栏Aloys的开发之路

OOAD与UML笔记

UML基础介绍 1.UML的定义 统一建模语言(UML)是一种图形化的语言,它可以帮助我们在OOAD过程中标识元素、构建模块、分析过程并可通过文档说明系统中的重...

21580

扫码关注云+社区

领取腾讯云代金券