公益网站 速度稍慢 请您谅解

作者:happyfly

2022.03.01

道棋棋盘可以视为一个16x16的矩阵,矩阵的元素有三种状态:EMPTY, BLACK, WHITE, 分别代表空点、黑子和白子。

先初始化一个矩阵:

import numpy as np

EMPTY = 0
BLACK = 1
WHITE = -1
SIZE = 16

board = np.zeros((SIZE,SIZE))
print(board)

[[0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
 [0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
 [0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
 [0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
 [0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
 [0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
 [0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
 [0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
 [0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
 [0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
 [0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
 [0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
 [0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
 [0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
 [0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
 [0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]]

由于道棋左右上下联通的特性,矩阵平移、旋转、翻转都不影响棋盘的逻辑。下面我们就用程序实现这些变换:

#为了实现平移,先定义一个函数boardshift,它有三个参数:要操作的矩阵、水平平移步数、垂直平移步数
def boardshift(matrix, v_shift, h_shift):
    h, w = matrix.shape
    matrix=np.vstack((matrix[(h - v_shift % h):,:],matrix[:(h - v_shift % h),:]))
    matrix=np.hstack((matrix[:,(w - h_shift % w):],matrix[:,:(w - h_shift % w)]))
    return matrix
board = boardshift(board, 2, -10) #向下移动两步,向左移动10步

#矩阵旋转比较简单,使用numpy.rot90方法即可,其中参数k是逆时针旋转90度的次数
board = np.rot90(board, k = 2)

#矩阵翻转:
board = board.T                  #对角线为轴的翻转,相当于行列变换
board = np.flip(board, axis = 0) #上下翻转
board = np.flip(board, axis = 1) #左右翻转

着子可以通过改变矩阵元素的值来实现:

#黑棋在第十三行第四列着子
board[12,3] = BLACK
print(board)

[[0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
 [0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
 [0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
 [0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
 [0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
 [0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
 [0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
 [0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
 [0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
 [0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
 [0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
 [0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
 [0. 0. 0. 1. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
 [0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
 [0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
 [0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]]

通过矩阵的操作,可以让道棋棋盘在前两手棋实现棋盘标准化:

1、既然第一手黑棋走在任意点都是等价的,那么就定义第一手黑棋的坐标为0,0

2、第二手白棋理论上只有44种可能性,那么就把第二手白棋固定在某个特定的范围内(下面矩阵下划线的位置)

#黑棋在第十三行第四列着子
board[12, 3] = BLACK

board = boardshift(board, -12, -3) #把第一个黑子移动到0,0位置。

print(board)

[[1. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
 [0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
 [0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
 [0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
 [0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
 [0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
 [0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
 [0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
 [0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
 [0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
 [0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
 [0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
 [0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
 [0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
 [0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
 [0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]]

现在白棋着子:

board[9, 13] = WHITE
print(board)

[[ 1.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.]
 [ 0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.]
 [ 0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.]
 [ 0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.]
 [ 0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.]
 [ 0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.]
 [ 0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.]
 [ 0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.]
 [ 0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.]
 [ 0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0. -1.  0.  0.]
 [ 0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.]
 [ 0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.]
 [ 0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.]
 [ 0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.]
 [ 0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.]
 [ 0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.]]

白棋横坐标和纵坐标都大于8,说明黑棋距离白棋近的方向是在另一边,所以需要先水平和垂直平移-1步,把第一手黑棋挪到右边和下边(右下角),使黑棋和白棋距离上最近,然后再逆时针旋转180度(等效于上下翻转加上左右翻转):

board = boardshift(board, -1, -1)
board = np.rot90(board, k = 2)
print(board)

[[ 1.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.]
 [ 0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.]
 [ 0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.]
 [ 0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.]
 [ 0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.]
 [ 0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.]
 [ 0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.]
 [ 0.  0.  0. -1.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.]
 [ 0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.]
 [ 0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.]
 [ 0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.]
 [ 0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.]
 [ 0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.]
 [ 0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.]
 [ 0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.]
 [ 0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.]]

此时白棋恰好落在前面矩阵中下划线的范围内,如果白棋的横坐标大于纵坐标,则还需要用T方法做一次以对角线为轴的翻转。

棋盘的坐标按此规则固定后,道棋不同开局就能够进行比较了。这也可能有利于深度学习。

下面是完整的测试程序:

#-*-coding:utf-8-*-

import numpy as np
import random

EMPTY = 0
BLACK = 1
WHITE = -1
SIZE = 16

board = np.zeros((SIZE,SIZE))

def boardshift(matrix, v_shift, h_shift):
    h, w = matrix.shape
    matrix=np.vstack((matrix[(h - v_shift % h):,:],matrix[:(h - v_shift % h),:]))
    matrix=np.hstack((matrix[:,(w - h_shift % w):],matrix[:,:(w - h_shift % w)]))
    return matrix

# First Black Move

x = int(input('Please input x for the first move (0-' + str(SIZE-1) + '): '))
y = int(input('Please input y for the first move (0-' + str(SIZE-1) + '): '))

board[y, x] = BLACK
print('Black move: \n', board)

board = boardshift(board, -y, -x)
print('First Black stone shifted: \n', board)

# Second White Move

rand = input('Randomly generated coordinates for the second move (N/y): ')
if rand == 'y':
    y = random.randint(1, SIZE/2)
    x = random.randint(1, y)
else:
    x = int(input('Please input x for the second move (0-' + str(SIZE-1) + '): '))
    y = int(input('Please input y for the second move (0-' + str(SIZE-1) + '): '))

board[y, x] = WHITE
print('White move: \n', board)

if x > SIZE/2:
    board = boardshift(board, 0, -1)
    board = np.flip(board, axis = 1)
    x = SIZE - x

if y > SIZE/2:
    board = boardshift(board, -1, 0)
    board = np.flip(board, axis = 0)
    y = SIZE - y

if x > y:
    board = board.T

print('Standardized board coordinates: \n', board)

上面实现的这些变换只是计算机背后的逻辑,对弈者不需要知道程序中发生了什么。

大家也可以看到,道棋没有边角的特点,使编程更加容易。利用boardshift函数,计算机前端很容易实现窗口平移。在图形界面中画棋盘也变得更加灵活,不必象围棋一样先画一个有边缘的棋盘,道棋只需要在相应的坐标位置画上“┼”或“●”或“○”就可以了。

#字符界面下打印棋盘
for line in board:
    print_line = ''
    for point in line:
        if point == 1:
            print_line += '●'
        elif point == -1:
            print_line += '○'
        else:
            print_line += '┼'
    print(print_line)

返回谈棋论道

道棋对局