3.4 创建你第一个AI:可以想见的最弱AI
在实现了围棋棋盘和游戏状态类之后,您可以构建您的第一个围棋AI。这个机器人将是一个很弱的玩家,但它将为你的后续所有改进奠定基础。首先,您需要定义所有机器人都要调用的接口,并把定义放入agent文件下中的base.py中。
class Agent:
def __init__(self):
pass
# 根据棋盘盘面状态去选择相应落子
def select_move(self,game_state):
raise NotImplementedError() # 预留一个借口先不实现,让子类去实现
就是这样一种方法,机器人所做的一切都是在当前游戏状态下选择一个落子。当然,在内部,这可能需要十分复杂的操作,如评估目前的棋盘盘面,但玩游戏,这就是我们的机器人永远需要的。
我们的第一个实现将尽可能的弱智:它将随机选择任何有效的移动,并且不去填它自己的真眼。你可以把这个随机AI放在agent下面。回想起第二章,在围棋中,学生的排名通常从30级到1级。按这个比例,你的随机机器人在30级水平,是绝对的初学者。
import random
from dlgo.agent.base import Agent
from dlgo.agent.helpers import is_point_true_eye
from dlgo.goboard_slow import Move
from dlgo.gotypes import Point
# 随机落子AI
class RandomBot(Agent):
def select_move(self, game_state):
# 合法落子集合
valid_moves = []
for r in range(1, game_state.board.num_rows+1):
for c in range(1, game_state.board.num_cols+1):
point = Point(row=r,col=c)
# 保证合法且不是自填眼位
if game_state.is_valid(Move.play(point)) and not is_point_true_eye(point,game_state.current_player):
valid_moves.append(point)
if not valid_moves:
print("over")
return Move.play(random.choice(valid_moves))
现在保证你的agent模块是如下结构(空的_init_py以初始化模块):
dlgo
agent
__init__.py
helpers.py
base.py
naive.py
最后,您可以设置一个驱动程序,在随机落子AI的两个实例之间进行完整的游戏。首先,您要定义方便的功能函数,例如在控制台中打印整个棋盘或个人落子。
围棋棋盘的坐标可以用多种方式来指定,但在欧洲,用字母表的字母来标记列是最常见的,从A开始,行用递增的数字表示,从1开始。在一个标准的19×19棋盘上,左下角是A1,右上角是T19。请注意,根据惯例,省略字母I从而避免与1混淆。
先定义一个字符串变量COLS=‘ABCDEFGHJ KLMNOPQRST’,其字符代表围棋棋盘的列。若要在命令行上显示棋盘,棋盘上的空点就用点表示,x表示黑棋,o表示白棋。把下面的代码放入一个dlgo文件夹下的新文件utils.py,print_move该函数打印用户操作到命令行,print_board函数用来打印当前的棋盘。您将此代码放入dlgo模块之外的一个名为bot_v_bot.py的文件中。
from dlgo import gotypes
COLS = 'ABCDEFGHJKLMNOPQRST'
STONE_TO_CHAR = {
None: ' . ',
gotypes.Player.black: ' x ',
gotypes.Player.white: ' o ',
}
def print_move(player, move):
if move.if_pass:
move_str = 'passes'
elif move.if_resign:
move_str = 'resigns'
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(gotypes.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]))
您可以设置一个脚本,启动两个随机落子AI,在一个9×9棋盘上相互对弈,直到他们游戏结束。
from dlgo.agent import naive
from dlgo import goboard
from dlgo import gotypes
from dlgo.utils import print_board,print_move
import time
def main():
board_size = 9
game = goboard.GameState.new_game(board_size)
if game is None:
print("hh")
# 两个随机AI
bots = {
gotypes.Player.black:naive.RandomBot(),
gotypes.Player.white:naive.RandomBot()
}
print("游戏开始")
while not game.is_over():
time.sleep(0.3) # 你将计时器设置为0.3秒,这样机器人的操作就不会打印得太快,无法观察到。
print(chr(27)+"[2J") # 清屏
print_board(game.board)
bot_move = bots[game.current_player].select_move(game)
print_move(game.current_player, bot_move)
game = game.apply_move(bot_move)
print("游戏结束")
if __name__ == '__main__':
main()
最后结果(我按它的代码得到的结果,完成了两个随机AI对弈,由结果可知,两个AI成功防止了填真眼。
这个机器人不仅弱,而且是一个令人沮丧的对手:它会顽固地落子,直到没有可下的地方,而且,不管你让这些AI互相对弈多少时间,也从不涉及学习,因此这个随机AI将永远保持在当前的水平。
在整本书的其余部分,你会慢慢地在这两方面都有所改进,去建立一个更有趣和强大的围棋引擎。