Python 实现黑白棋

Python实现黑白棋

  • 题目要求
    • 电脑的策略
    • 游戏结束的条件
  • 解题思路
    • 关键逻辑
  • 关键函数
    • Init_board:
    • printBoard:
    • computer_move:
    • human_move:
    • check_board:
    • check_legal_move:
    • gameover:
    • saveinfo:
    • main
  • 后记
    • 程序可能的优化方向

题目要求

电脑的策略

对每个可能的落子位置,都进行尝试,计算该位置的“分值” (可以翻转的对手棋子数量),分值越高则在该位置落子越有利。计算每个可能位置的分值,选择最大值位置落子。可能有2个或多个棋盘格有相同的分值。这种情况下,选择行字母最小的棋盘格。如果两个棋盘格分值相同且在同一行,则选择列字母较小的棋盘格

游戏结束的条件

1)整个棋盘满了;
2)一方的棋子已经被对方吃光;
3)两名玩家都 没有可以落子的棋盘格;
4)一方落子在非法位置。
前 3 种情况以棋子数目来计算胜负,棋子多的一方获 胜;第 4 种情况判定对方获胜。

解题思路

关键逻辑

1)不论是电脑下棋还是人类下棋,都需要先判断是否有字可落
2)人类下棋之前,要判断落子是否合法
3)在判断落子是否合法的时候,要注意棋盘的边界
因为python的列表存在下标为-1的情况,故要注意下标的下界,另一方面,因为黑白棋要考虑尽头那个棋子是否为同色的情况,所以要注意当前搜索棋子和同方向的下一个棋子(潜在的同色可能性)是否越界。
举例:4*4棋盘的“黑 白 白 白” ,如果不考虑下一个棋子的越界情况的话,会出现从黑开始绕一个圈回来又查找到黑从而把三个白棋翻转的错误情况
4)其实翻转、检测落子是否合法,计算落子的分值用的都是同一套循环逻辑,所以只写一个然后基本复制黏贴就好了
5)用多次sort进行不同权重的比较
6)游戏结束的几种情况要分情况讨论

关键函数

Init_board:

读入棋盘大小 n(n 为偶数,且 4≤n≤26),按照要求初始化棋盘。程序使用如下字 符表示每个棋盘格的状态:
. – 未被占用 X – 被黑棋占用 O – 被白棋占用

def Init_board():
   global board
   board = [[0] * l for row in range(l)]  #用列表生成式生成棋盘
   board[int(l / 2 - 1)][int(l / 2 - 1)] = -1
   board[int(l / 2 - 1)][int(l / 2)] = 1
   board[int(l / 2)][int(l / 2 - 1)] = 1
   board[int(l / 2)][int(l / 2)] = -1    #四个初始棋子的摆放
   return board
    #在这个处理中我们用0代表未落子,1代表黑棋,-1代表白棋

printBoard:

输出棋盘。例如:4×4 棋盘的初始状态如下:
a b c d
a . . . .
b . O X .
c . X O .
d . . . .

def printBoard():
    l = len(board)
    print(' ', end = ' ')
    for i in range(l):
        print(chr(ord('a') + i), end = ' ')
    print()
    for i in range(l):
        print(chr(ord('a') + i), end = ' ')
        for j in range(l):
            if (board[i][j] == 0):
                print('.', end = ' ')
            elif (board[i][j] == 1):
                print('X', end = ' ')
            else:
                print('O', end = ' ')
        print()

computer_move:

计算机下棋。落子位置表示为“行列字母”的格式,如:“ba”代表棋子 落在行 b 列 a。

def computer_move(row, col, color):             #电脑行棋和棋子翻转
    global board
    if (check != 0):                            #check中存储的是该棋子的价值(翻转的棋子数),不为0则合法
        board[row][col] = color                 #向规定地点放上棋子
        direction = ((-1, -1), (-1, 0), (-1, 1), (0, -1), (0, 1), (1, -1), (1, 0), (1, 1))
        for i in range(8):
            z = 1                               #对8个方向进行搜索
            while (0<= row + (z+1) * direction[i][0] < l) and (0<= col + (z+1) * direction[i][1] < l) and (
                board[row + z * direction[i][0]][col + z * direction[i][1]] == -color):#搜索进行的条件,搜索的下一个位置没有超过系统边界(因为尽头是一个相同的棋子所以要多查找一步),当前位置的棋子是一个异色的棋子。
                z += 1                                                                 #这里涉及到一个问题就是python列表值是存在-1值的,所以要规定列表搜索的下界只能到0,不然本质上会查找到列表最后一个(下标为-1)的值
                if board[row + z * direction[i][0]][col + z * direction[i][1]] == color: #如果查找到了尽头的那个同色棋子
                    for j in range(z):
                        board[row + j * direction[i][0]][col + j * direction[i][1]] = color #那就把中间的棋子全部置为同色并退出这次搜索
                    break                  

human_move:

用户下棋

def human_move(row, col, color):                #人类行棋和棋子翻转,与电脑行棋类似
    global board
    if (check != 0):
        board[row][col] = color
        direction = ((-1, -1), (-1, 0), (-1, 1), (0, -1), (0, 1), (1, -1), (1, 0), (1, 1))
        for i in range(8):
            z = 1
            while (0<=row + (z+1) * direction[i][0] < l) and (0 <= col + (z+1) * direction[i][1] < l) and (
                board[row + z * direction[i][0]][col + z * direction[i][1]] == -color):
                z += 1
                if board[row + z * direction[i][0]][col + z * direction[i][1]] == color:
                    for j in range(z):
                        board[row + j * direction[i][0]][col + j * direction[i][1]] = color
                    break

check_board:

检测游戏是否结束

def check_board():                               #检测本局游戏是否结束
    a1 = 1                                       #用来标记棋盘是否下满
    a21 = 1                                      #检测棋盘中是否还有黑棋
    a22 = 1                                      #检测棋盘中是否还有白棋
    a4 = 0                                       #检测当前落子位置是否合法
    for i in range(l):
        for j in range(l):                       #遍历整个棋盘
            if (board[i][j] == 0):                #如果存在空位
                a1 = 0
            elif (board[i][j] == 1):              #如果存在黑棋
                a21 = 0
            else:                                 #如果存在白棋
                a22 = 0

    if not (check):                               # check为0则不存在翻转,落子合法
        a4 = 1
    if (a1 or (a21 and a22) or (not (xp or op)) or a4):
        if (a1 or (a21 and a22) or (not (xp or op))):     # xp和op代表了双方是否存在落子位置
            if (not (xp or op)):                          #如双双置0,则证明双方都无落子位置
                print("Both player have no vaild move!")
            gameover('score')                             #这些都是积分计算胜负关系

        else:                                             #如果a4出现了,则落子非法,计算胜负关系
            gameover('illegal')

check_legal_move:

检测颜色为 color 的棋子落在棋盘格(row, col) 上是否合法

 def check_legal_move(row, col, color):          #检测落子位置是否合法
    direction = ((-1, -1), (-1, 0), (-1, 1), (0, -1), (0, 1), (1, -1), (1, 0), (1, 1))
    score = 0

    for i in range(8):                                    #思路与落子的思路类似,但不同的是这里当搜索到最后的同色棋子时,进行加和操作而非翻面操作 
        zscore = 1
        while (0 <= (row + (zscore+1) * direction[i][0]) < l) and (0 <= (col + (zscore+1) * direction[i][1]) < l) and (
                (board[row + zscore * direction[i][0]][col + zscore * direction[i][1]]) == -color):
            if (board[row + (zscore + 1) * direction[i][0]][col + (zscore + 1) * direction[i][1]] == color):
                score += zscore
                break
            zscore += 1
    return score

gameover:

游戏结束,统计得分,输出结果

def gameover(s):                                          #游戏结束判定
   global s1
   global condition
   global black
   global white

   condition = 0                                         #将游戏标记置为 0,游戏停止
   if s == 'score':                                      #如果是分数过程,则计算黑白棋的个数
       print('Game Over!')
       black = 0
       white = 0

       for i in range(l):
           for j in range(l):
               if board[i][j] == 1:
                   black += 1
               if board[i][j] == -1:
                   white += 1
       if (black > white):
           print('X : O =', black, ':', white)
           print('X player wins!')
       elif (black < white):
           print('O player wins!')
           print('X : O =', black, ':', white)
       else:
           print('Draw!')
       s1 = str(black) + 'to' + str(white)              # s1 中存储的是要输进文件去的文本
       return s1
   if s == 'illegal':
       print('Invaild move!')
       s1 = 'Invaild move!'
   if s == 'gg':
       print('Human gave up!')
       s1 = 'Human gave up!'
   print('Game Over!')
   if (color == 1):                                      #这里是非法输入和gg投子的输出设置,color在主程序中更改
       print('O player wins.')
   else:
       print('X player wins.')
   return s1

saveinfo:

把每次人机对弈的信息作为一行写入文件reversi.csv中,这些信息包括:游戏 开始的时间、单次游戏使用的时间、棋盘大小、黑棋玩家、白棋玩家、游戏比分,信息之间使用逗号字 符分隔。

def saveinfo():                                            #文件存储
 time3 = time.time()                                    #游戏结束时间
 long = time3-time2                                     #游戏持续时长
 f = open('Reversi.csv', 'a+')
 s = str(time1) + ',' + str(long) + ',' + str(l) + '*' + str(l) + ',' + Xplayer + ',' + Oplayer + ',' + s1
 f.write(s)                                             #向文件中输入
 f.write('\n')
 f.close()

main

import datetime
import time
time1 = datetime.datetime.now().strftime('%Y-%m-%d %T')    #保存游戏时间
time2 = time.time()                                        #游戏开始时间
l = int(input('Enter the board dimension:'))              # l 中保存了棋盘的长度
board = Init_board()                                       #初始化棋盘
x1 = input('Computer plays (X/O):')                        # x1中保存电脑的棋子颜色
if (x1 == 'X'):                                            # 电脑执黑 
 hcolor = -1
 Xplayer = 'computer'
 Oplayer = 'human'                                      #这个是要输进文件里的东西
 condition = 1                                          #游戏标记置1,游戏进行

 printBoard()                                           #打印 、
 while condition:                                       #如果游戏标志为1(没进gameover),则可以继续运行
     legal = []
     for i in range(l):
         for j in range(l):
             if (board[i][j] == 0):
                 if (check_legal_move(i, j, 1) != 0):
                     legal.append([i, j, check_legal_move(i, j, 1)])  #将所有可下位置存进legal列表中
 	 legal.sort(key=lambda x: x[1], reverse=False)
     legal.sort(key=lambda x: x[0], reverse=False)
     legal.sort(key=lambda x: x[2], reverse=True)                     #对列表进行先值,再行,再列的排序
     if (legal != []):                                                #如果存在合法输入
         row = legal[0][0]
         col = legal[0][1]
         color = 1
         check = check_legal_move(row, col, color)
         xp = 1                                                 #证明X仍可下
         op = 1                                                 #证明O仍可下
         computer_move(row, col, 1)                                   #将分值存进check里,进行落子
         print('Computer places X at', chr(ord('a') + row), chr(ord('a') + col), '.')
     else:
         xp = 0                                                       #如果不存在落子位置
         print('X player has no vaild move.')                         #不进行落子


     printBoard()                                            #进行棋盘打印
     check_board()                                           #进行检测棋局是否结束
     if (condition != 0):                                    #如果棋局没有结束的话进行人类行棋
         legal = []                   
         for i in range(l):
             for j in range(l):
                 if board[i][j] == 0:
                     if (check_legal_move(i, j, -1) != 0) and (board[i][j] == 0):
                         legal.append([i, j, check_legal_move(i, j, -1)])   #检测是否有行棋位置
         if (legal != []):
             inter = input('Enter move for O (Rowcol):')
             if inter == 'gg':                        #贴心的投降设置
                 gameover('gg')
                 break
             row = ord(inter[0]) - 97          #把输入的字母通过ASCII码变成输出
             col = ord(inter[1]) - 97
             color = -1
             if (row>=l) or (col>=l) or (row<0) or (col <0):          #如果输入超过边界,则为非法输入
                 gameover('illegal')
                 break
             
             check = check_legal_move(row, col, color)    #检测落子是否存在翻转
             xp = 1
             op = 1
             human_move(ord(inter[0]) - 97, ord(inter[1]) - 97, -1) #人类落子
             check_board()                                     #检测当前棋盘是否满足结束条件
             if check:
                 printBoard()                                     #如果通过,则进行打印
         else:
             op = 0                                                #白棋无棋可下                         
             print("O player has no vaild move.")
 saveinfo()      #游戏结束之后进行游戏记录的保存


else:  #之后为电脑执白棋的情况,与上文黑棋的情况类似
 hcolor = 1
 Oplayer = 'computer'
 Xplayer = 'human'
 condition = 1

 printBoard()
 while condition:
     legal = []
     for i in range(l):
         for j in range(l):
             if (board[i][j] == 0):
                 if (check_legal_move(i, j, 1) != 0):
                     legal.append([i, j, check_legal_move(i, j, 1)])
     if (legal != []):
         inter = input('Enter move for X (Rowcol):')
         if inter == 'gg':
             gameover('gg')
             break

         row = ord(inter[0]) - 97
         col = ord(inter[1]) - 97
         color = 1
         if (row>=l) or (col>=l) or (row<0) or (col <0):
             gameover('illegal')
             break

         check = check_legal_move(row, col, color)
         xp = 1
         op = 1
         human_move(ord(inter[0]) - 97, ord(inter[1]) - 97, 1)
         check_board()
         if check:
             printBoard()
     else:
         op = 0
         print("O player has no vaild move.")

     if condition:
         legal = []
         for i in range(l):
             for j in range(l):
                 if (board[i][j] == 0):
                     if (check_legal_move(i, j, -1) != 0):
                         legal.append([i, j, check_legal_move(i, j, -1)])
         legal.sort(key=lambda x: x[1], reverse=False)                
         legal.sort(key=lambda x: x[0], reverse=False)
         legal.sort(key=lambda x: x[2], reverse=True)
         if (legal != []):
             xp = 1
             op = 1
             row = legal[0][0]
             col = legal[0][1]
             color = -1
             check = check_legal_move(row, col, color)
             computer_move(row, col, -1)
         else:
             xp = 0
             print("X player has no vaild move.")
         check_board()
         printBoard()
         print('Computer places O at', chr(ord('a') + row), chr(ord('a') + col), '.')

 saveinfo()

后记

程序可能的优化方向

程序可能出现的错误!存在一种情况就是一方本来无棋可下,另一方行棋之后突然有棋可下了!所以需要重置“是否有棋可下”的条件码,这里等到大作业截止之后再做修改
1)将电脑执黑或执白的情况分开来讨论太麻烦了(相当于代码量*2),能用一个函数解决它吗?
2)其实判断落子位置,分值和是否合法用的也是同一套函数,可以使用函数进行代码的复用,就不用写那么多了。
3)因为后面基本就是用pycharm来做逐步debug了,所以一些判断和list语句可能写的没那么好看
4)程序的稳定性有待检验(实在是懒得跟电脑下那么多盘棋)
5)gameover的函数多入口情况是否可以优化?

你可能感兴趣的:(Python,大作业)