1. 程式人生 > >AI實現的兩種方案,暴力推演與因果率

AI實現的兩種方案,暴力推演與因果率

AI實現的兩種方案,暴力推演與因果率

學習PYTHON兩個月,寫個小遊戲練手。也為以後找工作做儲備。
從最簡單的九格棋入手。
在這裡插入圖片描述
九格棋玩法簡單,橫向,縱向,斜向三子連線則為勝。
在這裡插入圖片描述

基本設計構件有:
一、GUI介面。
介面我選用PYGAME做的。因為PYGAME中沒有提供按鈕等相關控制元件,所以只好自己做一下簡單的按鈕、資訊框等。
二、遊戲規則。
需要裁判來限定落子規則,判定勝負結果。
三、AI
最簡單的方法是找出每一步走法只的每種可能性,遍歷這些可能性,並遞迴推演到最後一步(九格棋最多至9步)。將推演的勝負結果儲存,然後計算每種走法的勝負數,及勝負概率。
這種方法可謂簡單除暴。只有9層的推演,生成50萬條記錄,單執行緒用時6小時。
在這裡插入圖片描述


這種方法生成的資料最全面完整,簡單使用勝率排序就能達到很好的效果。但是有些特別情況,單一勝率就不是最好的選擇,例如勝率49%,負率51%,雖然勝率很高,但負率更高,這種冒險走法就不是最好的選擇。這時就要加入其它引數做綜合排序。
暴力推演效果雖好,但用時太長,只適合可能性較少的模型。試想一下,如果用這種方法去推演五子棋,跳棋,象棋,圍棋,要產生多大的資料,又要耗時多久?顯然是不現實的。所以需要用因果率方案實現AI,具體方法就是有限推演和互動經驗。
在九格棋遊戲中也會記錄每次對局的過程和結果,做為綜合排序的一項引數。

製作過程:
先製作單人單機,製作介面和控制元件。

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @File  : 九子棋_單人.py
# @Author: Jin peng
# @Date  : 2018/12/10 0010
# @Desc  : 

# import pygame
# from pygame.locals import *
import sys,time,os
from 控制元件類 import *
from 裁判類 import *

class 棋子類(pygame.sprite.Sprite):
    # 黑方=黑方
    # 白方=白方
    def __init__(self, 父, x, y, style='黑方'):
        super().__init__()
        self.父 = 父
        self.style = style
        if style == '黑方':
            self.image = 黑方
        else:
            self.image = 白方
        self.rect = self.image.get_rect()
        # self.time1  =   round(time.time())
        self.rect.centerx = x
        # pygame.rect.Rect.centerx
        self.rect.centery = y
        # self.update()
    def update(self):
        self.父.blit(self.image, self.rect)

def 畫格(bg1):
    for i in range(4):
        pygame.draw.line(bg1, [100, 100, 100], [i*200, 0], [i*200, 600], 3)
        pygame.draw.line(bg1, [100, 100, 100], [0,i * 200], [600,i * 200], 3)
    # pygame.image.save(bg1,'bg111.jpg')

def 退出程式():
    pygame.quit()
    sys.exit()

def checkForQuit():
    for event in pygame.event.get(QUIT):  # get all the QUIT events
        退出程式()  # terminate if any QUIT events are present
    for event in pygame.event.get(KEYUP):  # get all the KEYUP events
        if event.key == K_ESCAPE:
            退出程式()  # terminate if the KEYUP event was for the Esc key
        pygame.event.post(event)  # put the other KEYUP event objects back

def 顯示所有按鈕():
    for theclass in 按鈕類.allobj:
        theclass.顯示()

def 檢測移動():
    for event in pygame.event.get(MOUSEMOTION):
        mx, my = event.pos
        for theclass in 按鈕類.allobj + 文字框類.allobj:
            movein = theclass.進入(mx, my)
            if movein:
                音效1.play()
                break

def 檢測按下():
    for event in pygame.event.get(MOUSEBUTTONDOWN):
        # 發生滑鼠左鍵按下事件,傳遞給所有按鈕類控制元件
        if event.button == 1:
            mx, my = event.pos
            for theclass in 按鈕類.allobj:
                click = theclass.點選(mx, my)
                if click:
                    執行按鈕(theclass)
                    break
        # 發生滑鼠滾輪事件,傳遞給所有文字框類控制元件
        elif event.button in [4,5]:
            mx, my = event.pos
            for theclass in 文字框類.allobj:
                theclass.滾動(mx, my,event.button)
            # print('上滾')

def 檢測放開():
    for event in pygame.event.get(MOUSEBUTTONUP):
        if event.button == 1:
            mx, my = event.pos
            if 0 < mx < 600 and 0 < my < 600:
                x = mx // 200
                y = my // 200

                落子(x, y)
            for theclass in 按鈕類.allobj:
                theclass.放開()

def 落子(x, y):
    global 狀態, 落子方
    ok = 裁判.down(y,x,落子方)
    if ok:
        px = x  * 200 + 100
        py = y  * 200 + 100
        新棋子 = 棋子類(screen, px, py, 落子方)
        棋子組.add(新棋子)
        音效2.play()
        文字框.print(落子方+' x='+str(x)+' y='+str(y))
        遊戲狀態()
        return
    else:
        音效3.play()
    # print(棋子組.sprites())

def 遊戲狀態():
    global 狀態, 落子方, 步數
    落子方1 = 落子方
    狀態, 落子方, 步數 = 裁判.遊戲狀態()
    msn = 狀態
    if 狀態 == '等待':
        msn = '等待 ' + 落子方 + ' 落子'
    elif 狀態 == '勝利':
        msn = 落子方1 + ' 勝利!'
        音效勝利.play()
        文字框.print(msn)
    else:
        文字框.print(msn)
    資訊框1.標題 = msn
    資訊框1.更新()
    if 步數>1:
        悔棋按鈕.更改狀態(1)
        儲存按鈕.更改狀態(1)
    else:
        悔棋按鈕.更改狀態(4)
        儲存按鈕.更改狀態(4)

def 執行按鈕(obj):
    if obj == 退出按鈕:
        退出程式()
    elif obj == 開始按鈕:
        if 開始按鈕.標題=='開始':
            if 裁判.開始():
                開始按鈕.標題 = '結束'
                棋子組.empty()
                遊戲狀態()
        elif 開始按鈕.標題=='結束':
            if 裁判.結束():
                開始按鈕.標題 = '開始'
                棋子組.empty()
                遊戲狀態()
        # 開始遊戲按鈕.更改狀態(4)
    elif obj == 悔棋按鈕:
        if 裁判.悔棋():
            棋子組.remove(棋子組.sprites()[-1])
            # print(棋子組.sprites())
            遊戲狀態()
    elif obj == 儲存按鈕:
        走棋資料=裁判.返回儲存()
        now=time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
        csvdata=[[now],走棋資料]
        csvfile=open('save.csv','a')
        csvfile.write(str(csvdata)+'\n')
        csvfile.close()
        文字框.print('儲存為'+now)
    elif obj == 讀取按鈕:
        存檔=讀取存檔(1)
        if not 存檔:
            return
        文字框.print('讀取' + str(存檔[0]))
        # print(存檔[1], '----')
        裁判.開始()
        棋子組.empty()
        遊戲狀態()
        for i in 存檔[1]:
            落子(i[1],i[0])

def 讀取存檔(n=0):
    filename='save.csv'
    if not (os.path.isfile(filename)):
        文字框.print('沒有儲存資料')
        return
    csvfile=open('save.csv','r')
    csvdata=csvfile.readlines()
    if n!=0:
        return eval(csvdata[n])
    # print(type(csvdata))
    # csvdata=[]
    # csvdata=temp
    # i=0
    # for line in csvfile:
    #     i+=1
    #     # line = csvfile.readline()
    #     linedata=eval(line)
    #     if n!=0:
    #         return linedata
    #     csvdata.append(linedata)
    #     print(line,'----',i)
    return csvdata

pygame.mixer.pre_init(44100, 16, 2, 4096)
pygame.init()
screen = pygame.display.set_mode((800, 600), 0, 32)
pygame.display.set_caption('九子棋_單人單機')
clock = pygame.time.Clock()
thefont = pygame.font.Font('FZSHJW.TTF', 18)

音效1 = pygame.mixer.Sound("音效_進入.wav")
音效2 = pygame.mixer.Sound("音效_落子.wav")
音效3 = pygame.mixer.Sound("音效_錯誤.wav")
音效勝利 = pygame.mixer.Sound("音效_勝利.wav")
音樂1 = pygame.mixer.music.load('音樂1.wav')
pygame.mixer.music.play(-1)

bg1 = pygame.image.load('bg1.jpg')
# 畫格(bg1)
bg2 = pygame.image.load('bg2.jpg')
黑方 = pygame.image.load('棋子1.png')
白方 = pygame.image.load('棋子2.png')
棋子組 = pygame.sprite.Group()
控制元件組 = pygame.sprite.Group()

開始按鈕 = 按鈕類(父=screen, x=620, y=20, 標題='開始', 寬=80, 高=40)
退出按鈕 = 按鈕類(父=screen, x=710, y=20, 標題='退出', 寬=80, 高=40)
悔棋按鈕 = 按鈕類(父=screen, x=620, y=70, 標題='悔棋', 寬=80, 高=40, 狀態=1)
交換按鈕 = 按鈕類(父=screen, x=710, y=70, 標題='交換', 寬=80, 高=40, 狀態=4)
儲存按鈕 = 按鈕類(父=screen, x=620, y=120, 標題='儲存', 寬=80, 高=40, 狀態=4)
讀取按鈕 = 按鈕類(父=screen, x=710, y=120, 標題='讀取', 寬=80, 高=40)
資訊框1 = 標籤類(父=screen, x=620, y=170, 標題='計時', 寬=170, 高=40)
文字框 = 文字框類(父=screen, x=620, y=220, 寬=170, 高=320)
控制元件組.add(開始按鈕)
控制元件組.add(退出按鈕)
控制元件組.add(悔棋按鈕)
控制元件組.add(交換按鈕)
控制元件組.add(儲存按鈕)
控制元件組.add(讀取按鈕)
控制元件組.add(資訊框1)
控制元件組.add(文字框)

裁判 = 裁判類()
狀態, 落子方, 步數 = '', '', 0
文字框.print(裁判.game_str)
# for i in range(20):
#     文字框.print(str(i))
遊戲狀態()

# print(開始遊戲按鈕.主體.get_rect().x)
def main():
    while True:
        checkForQuit()
        screen.blit(bg1, (0, 0))
        screen.blit(bg2, (600, 0))

        # 顯示所有按鈕()

        檢測移動()
        檢測按下()
        檢測放開()

        棋子組.update()
        控制元件組.update()
        pygame.display.update()
        clock.tick(30)

if __name__ == '__main__':
    main()

控制元件類

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @File  : 控制元件類.py
# @Author: Jin peng
# @Date  : 2018/12/10 0010
# @Desc  : 
import pygame
from pygame.locals import *

class 按鈕類(pygame.sprite.Sprite):
    allobj = []  # 此類所有按鈕

    def __init__(self, 父=pygame.display, x=0, y=0, 寬=100, 高=30, 標題='按鈕', 狀態=1):
        super().__init__()
        self.父 = 父
        self.x = x
        self.y = y
        self.寬 = 寬
        self.高 = 高
        self.標題 = 標題
        self.狀態 = 狀態  # 0不可見,1正常,2滑鼠進入或選中,3點選,4無效
        self.邊 = 2
        self.字型大小 = 16
        self.底色 = [225, 245, 255]  # 當前背景色
        self.底色1 = [225, 245, 255]  # 原始背景色
        self.底色2 = [205, 225, 255]  # 啟用背景色
        self.底色3 = [200, 200, 250]  # 點選背景色
        self.底色4 = [180, 180, 180]  # 無效背景色
        self.邊色 = [150, 180, 190]
        self.字色 = [20, 20, 20]
        self.更新()
        self.allobj.append(self)

    def 更新(self):
        if self.狀態 == 0:
            # del self.主體
            self.主體 = None
            return
        elif self.狀態 == 1:
            self.底色 = self.底色1
        elif self.狀態 == 2:
            self.底色 = self.底色2
        elif self.狀態 == 3:
            self.底色 = self.底色3
        elif self.狀態 == 4:
            self.底色 = self.底色4

        self.主體 = pygame.Surface((self.寬, self.高), flags=SRCALPHA)
        self.主體.fill(self.邊色)
        self.主體.fill(self.底色, (self.邊, self.邊, self.寬 - self.邊 * 2, self.高 - self.邊 * 2))
        thefont = pygame.font.Font('FZSHJW.TTF', self.字型大小)
        textSurf = thefont.render(self.標題, True, self.字色)
        self.主體.blit(textSurf, [(self.主體.get_width() - textSurf.get_width()) // 2,
                                (self.主體.get_height() - textSurf.get_height()) // 2])

    def update(self, *args):
        if self.狀態 != 0:
            self.父.blit(self.主體, (self.x, self.y))

    def 進入(self, mx, my):

        if self.狀態 in [1, 2]:
            if self.x < mx < (self.x + self.寬) and self.y < my < (self.y + self.高):
                狀態 = 2
                movein = True
            else:
                狀態 = 1
                movein = False
            if self.狀態 != 狀態:
                self.更改狀態(狀態)
                return movein

    def 點選(self, mx, my):
        if self.狀態 in [1, 2]:
            if self.x < mx < (self.x + self.寬) and self.y < my < (self.y + self.高):
                self.更改狀態(3)
                self.run()
                return True

    def run(self):
        # print(self.標題, '被點選')
        pass

    def 放開(self):
        if self.狀態 == 3:
            self.更改狀態(1)

    def 更改狀態(self, 狀態):
        self.狀態 = 狀態
        self.更新()

class 標籤類(pygame.sprite.Sprite):
    allobj = []  # 此類所有控制元件

    def __init__(self, 父=pygame.display, x=0, y=0, 寬=100, 高=30, 標題='標籤', 狀態=1):
        super().__init__()
        self.父 = 父
        self.x = x
        self.y = y
        self.寬 = 寬
        self.高 = 高
        self.標題 = 標題
        self.狀態 = 狀態  # 0不可見,1正常,2滑鼠進入或選中,3點選,4無效
        self.邊 = 2
        self.字型大小 = 16
        self.水平對齊 = '左'
        self.垂直對齊 = '下'
        self.底色 = [225, 245, 255]  # 當前背景色
        self.底色1 = [225, 245, 255]  # 原始背景色
        self.底色2 = [35, 55, 65]  # 啟用背景色
        self.底色3 = [145, 165, 175]  # 點選背景色
        self.底色4 = [80, 80, 80]  # 無效背景色
        self.邊色 = [150, 180, 190]
        self.字色 = [20, 20, 20]
        self.更新()
        self.allobj.append(self)

    def 更新(self):
        if self.狀態 == 0:
            # del self.主體
            self.主體 = None
            return
        elif self.狀態 == 1:
            self.底色 = self.底色1
        elif self.狀態 == 2:
            self.底色 = self.底色2
        elif self.狀態 == 3:
            self.底色 = self.底色3
        elif self.狀態 == 4:
            self.底色 = self.底色4

        self.主體 = pygame.Surface((self.寬, self.高), flags=SRCALPHA)
        self.主體.fill(self.邊色)
        self.主體.fill(self.底色, (self.邊, self.邊, self.寬 - self.邊 * 2, self.高 - self.邊 * 2))
        thefont = pygame.font.Font('FZSHJW.TTF', self.字型大小)
        textSurf = thefont.render(self.標題, True, self.字色)
        self.主體.blit(textSurf, [(self.主體.get_width() - textSurf.get_width()) // 2,
                                (self.主體.get_height() - textSurf.get_height()) // 2])

    def 更改狀態(self, 狀態):
        self.狀態 = 狀態
        self.更新()

    def update(self, *args):
        if self.狀態 != 0:
            self.父.blit(self.主體, (self.x, self.y))

class 文字框類(pygame.sprite.Sprite):
    allobj = []  # 此類所有控制元件

    def __init__(self, 父=pygame.display, x=0, y=0, 寬=100, 高=30, 標題='文字框', 狀態=1):
        super().__init__()
        self.父 = 父
        self.x = x
        self.y = y
        self.寬 = 寬
        self.高 = 高
        self.標題 = 標題
        self.內容 = []
        self.狀態 = 狀態  # 0不可見,1正常,2滑鼠進入或選中,3點選,4無效
        self.邊 = 2
        self.滾動條寬=5
        self.滾動條高 = 高
        self.滾動條 = pygame.Rect(self.寬, 0, self.滾動條寬, self.滾動條高)
        self.字型大小 = 12
        self.字型 = 'FZSHJW.TTF'
        self.thefont = pygame.font.Font(self.字型, self.字型大小)
        self.字間距=2
        self.行間距=3
        self.字距=self.字型大小+self.字間距
        self.行距=self.字型大小+self.行間距
        self.每行字數 = (self.寬 - self.滾動條寬 - self.邊 * 2) // self.字型大小
        self.最大行數 = (self.高 - self.邊 * 2) // self.行距
        self.起始行 = 0
        self.記憶行數=100
        self.水平對齊 = '左'
        self.垂直對齊 = '下'
        self.底色 = [225, 245, 255]  # 當前背景色
        self.底色1 = [225, 245, 255]  # 原始背景色
        self.底色2 = [255,255,255]  # 啟用背景色
        self.底色3 = [145, 165, 175]  # 點選背景色
        self.底色4 = [80, 80, 80]  # 無效背景色
        self.邊色 = [150, 180, 190]
        self.滾動條色=[155, 155, 155]
        self.字色 = [20, 20, 20]
        self.更新()
        self.allobj.append(self)

    def 更新(self):
        if self.狀態 == 0:
            self.主體 = None
            return
        elif self.狀態 == 1:
            self.底色 = self.底色1
        elif self.狀態 == 2:
            self.底色 = self.底色2
        elif self.狀態 == 3:
            self.底色 = self.底色3
        elif self.狀態 == 4:
            self.底色 = self.底色4

        self.主體 = pygame.Surface((self.寬, self.高), flags=SRCALPHA)
        self.主體.fill(self.邊色)
        self.主體.fill(self.底色, (self.邊, self.邊, self.寬 - self.邊 * 2, self.高 - self.邊 * 2))
        self.print()

    def 更改狀態(self, 狀態):
        self.狀態 = 狀態
        self.更新()

    def 進入(self, mx, my):
        if self.狀態 in [1, 2]:
            if self.x < mx < (self.x + self.寬) and self.y < my < (self.y + self.高):
                狀態 = 2
                movein = True
            else:
                狀態 = 1
                movein = False
            if self.狀態 != 狀態:
                self.更改狀態(狀態)
                return movein

    def 滾動(self,mx,my,方向=4):
        if self.狀態 !=2:
            return False
        if len(self.內容)<self.最大行數:
            return False
        if not (self.x < mx < (self.x + self.寬) and self.y < my < (self.y + self.高)):
            return False
        if 方向==4:
            self.起始行-=1
            if self.起始行<0:
                self.起始行=0
        elif 方向==5:
            self.起始行 += 1
            if self.起始行 > len(self.內容)-self.最大行數:
                self.起始行 = len(self.內容)-self.最大行數
        # print(self.起始行)
        self.print()
    def print(self, msn=''):
        self.主體.fill(self.底色, (self.邊, self.邊, self.寬 - self.邊 * 2, self.高 - self.邊 * 2))
        if msn:
            while len(msn) > self.每行字數:
                tempstr = msn[:self.每行字數]
                self.內容.append(tempstr)
                msn = msn[self.每行字數:]
            self.內容.append(msn)
            if len(self.內容)>self.記憶行數:
                self.內容=self.內容[-self.記憶行數:]
            if len(self.內容)>self.最大行數:
                self.起始行 = len(self.內容) - self.最大行數
            else:
                self.起始行=0
        # print(self.起始行)
        ii=0
        for i in self.內容[self.起始行:]:
            textSurf = self.thefont.render(i, True, self.字色)
            self.主體.blit(textSurf, [3, ii * self.行距+3])
            ii+=1
        if len(self.內容)>self.最大行數:
            self.滾動條高=self.最大行數/len(self.內容)*self.高-self.邊*2
        else:
            self.滾動條高 = self.高 - self.邊 * 2
        self.滾動條=pygame.Rect(0,0,self.滾動條寬,self.滾動條高)
        self.滾動條.right=self.寬-self.邊
        if len(self.內容)>0:
            self.滾動條.top = self.邊+self.起始行/len(self.內容)*self.高
        self.主體.fill(self.滾動條色,self.滾動條)

    def update(self, *args):
        if self.狀態 != 0:
            self.父.blit(self.主體, (self.x, self.y))

裁判類

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @File  : 裁判類.py
# @Author: Jin peng
# @Date  : 2018/12/10 0010
# @Desc  : 

class 裁判類():
    game_str='''
    遊戲規則:二人遊戲。
    雙方分別執黑子、白子在3*3的9格棋盤上落子。
    甲方執黑子,乙方執白子。
    甲方先落子,然後雙方輪流落子。
    棋子只能落在棋盤格內。
    棋盤每格只能放一子,每格不能重複落子。
    獲勝條件:一方在棋盤內橫向、豎向、斜向有三子連續為勝。
    遊戲內容:使己方三子連續,並阻止對方三子連續。
    '''
    '''
    遊戲資料模型:
    棋盤是3*3列表
    列表中對應的值為0、1、2
    0表示空位,沒有落子。
    1表示甲方落的黑子。
    2表示乙方落的白子。
    STEP記錄落子的順序和位置。
    self.__step [x,y,落子方]
    '''

    def __init__(self):
        self.__棋盤=[]
        self.__step=[]
        self.__step_n=0
        self.__downing=0
        self.__落子方=''
        self.__狀態='等待開始'

    def down(self,x,y,落子方):
        if self.__狀態!='等待':
            return False
        if 落子方!=self.__落子方:
            return False
        if x<0 or x>2 or y<0 or y>2 :
            return False
        if self.__棋盤[x][y]!=0:
            return False
        self.__棋盤[x][y]=self.__downing
        if self.win(x,y):
            self.__狀態 = '勝利'
        elif self.__step_n>=9:
            self.__狀態 = '平局'
        else:
            self.__狀態 = '等待'
        self.__step.append([x,y,self.__downing])
        self.__step_n+=1
        self.__who()
        # print(self.__step)
        # for i in self.__棋盤:
        #     print(i)
        return True
    def __who(self):
        self.__downing=self.__step_n%2
        if self.__downing==1:
            self.__落子方 ='黑方'
        else:
            self.__落子方 = '白方'
            self.__downing=2
    def win(self,x,y):
        if self.__棋盤[0][y]==self.__downing and \
            self.__棋盤[1][y] == self.__downing and \
            self.__棋盤[2][y] == self.__downing:
            return True
        if self.__棋盤[x][0]==self.__downing and \
            self.__棋盤[x][1] == self.__downing and \
            self.__棋盤[x][2] == self.__downing:
            return True
        if self.__棋盤[0][0]==self.__downing and \
            self.__棋盤[1][1] == self.__downing and \
            self.__棋盤[2][2] == self.__downing:
            return True
        if self.__棋盤[0][2]==self.__downing and \
            self.__棋盤[1][1] == self.__downing and \
            self.__棋盤[2][0] == self.__downing:
            return True
        return False
    def 遊戲狀態(self):
        return self.__狀態,self.__落子方,self.__step_n
        pass
    def 開始(self):
        self.__棋盤=[[0 for i in range(3)] for i in range(3)]
        self.__step=[]
        self.__step_n=1
        self.__downing=1
        self.__落子方=''
        self.__狀態='等待'
        self.__who()
        return True
    def 結束(self):
        self.__棋盤=[]
        self.__step=[]
        self.__step_n=0
        self.__downing=0
        self.__落子方=''
        self.__狀態='等待開始'
        return True
    def 悔棋(self):
        if self.__step_n<2:
            return False
        the=self.__step[-1]
        # print(the)
        self.__棋盤[the[0]][the[1]]=0
        self.__step.pop()
        self.__step_n-=1
        self.__狀態 = '等待'
        self.__who()
        return True
    def 返回儲存(self):
        return self.__step
    def 可選位置(self):
        tmp=[]
        for i in range(len(self.__棋盤)):
            for j in range(len(self.__棋盤[i])):
                if self.__棋盤[i][j]==0:
                    tmp.append([i,j])
        return tmp

暴力推演主要程式碼:

    def 推演(self):
        累計 = 總數=0
        total = 1
        self.總計數 = [[0, 0, 0, 0, 0]]
        # [當前層已驗證數,當前層總數,到此層累計總數,此層所用時間,單步平均時間]
        for i in range(9, 0, -1):
            total = total * i
            總數+=total
            self.總計數.append([0, total, 總數, 0, 0])
            # print(i, total, 總數)
        推演資料鏈 = []
        self.推演起始層時間 = time.time()
        while self.一層:
            s1 = self.一層[0]
            if s1[-1] > 0 or s1[-2] > 0:
                # 此步勝利或平局,不能再向下推
                self.一層.pop(0)
                continue
            if s1[0] > 1:
                # 層數起過2,需要將此層之前的步鏈找出
                推演資料鏈 = self.返還推演資料鏈(s1)
                if 裁判.開始():
                    # 清空棋盤,重新開始
                    棋子組.empty()
                    遊戲狀態()
                    # 走出步鏈
                    for link in 推演資料鏈:
                        落子(link[5], link[4])
                    pass
            落子(s1[5], s1[4])
            self.up_id = s1[2]
            可選位置2 = 裁判.可選位置()
            self.走法數 = len(可選位置2)

            # print('落子資料鏈', self.落子資料鏈)
            # for cc in self.落子資料鏈:
            #     print(self.ai_data[cc[0]][cc[1]])

            while 可選位置2:
                i = 可選位置2[0]
                落子(i[1], i[0])
                重新整理窗體()
                累計 += 1
                self.總計數[self.第幾步][0] +=1
                self.the_id += 1
                self.x = i[0]
                self.y = i[1]
                狀態=裁判.遊戲狀態()[0]
                if 狀態 == '勝利':
                    # self.勝利數 = 1
                    勝利了 = 1
                    self.__更改資料鏈勝率(s1)
                elif 狀態 == '平局':
                    # self.平局數 = 1
                    平局了 = 1
                    self.__更改資料鏈平率(s1)
                else:
                    勝利了 = 0
                    平局了 = 0
                self.二層.append([ \
                    self.第幾步, self.走法數, self.the_id, self.up_id, self.x, self.y, \
                    self.勝利數, self.平局數, self.失敗數, 勝利了, 平局了])

                if len(self.ai_data) <= self.第幾步:
                    self.ai_data.append([self.第幾步])
                # self.ai_data[self.第幾步].append([ \
                #     self.第幾步, self.走法數, self.the_id, self.up_id, self.x, self.y, \
                #     self.勝利數, self.平局數, self.失敗數,勝利了,平局了])
                self.ai_data[self.第幾步] = self.二層.copy()

                if 裁判.悔棋():
                    棋子組.remove(棋子組.sprites()[-1])
                    遊戲狀態()
                可選位置2.pop(0)

            self.一層.pop(0)
            if len(self.一層) > 0:
                if 裁判.悔棋():
                    棋子組.remove(棋子組.sprites()[-1])
                    遊戲狀態()
            else:
                # self.save_txt(self.二層)
                self.一層 = self.二層.copy()
                # self.ai_data.append(self.二層)
                self.二層 = []
                self.the_id = 0
                self.__顯示推演用時()
                # self.save_txt(self.ai_data, 'alldata.txt')
                msn1 = '全部步數:' + str(self.第幾步) + ' / 9'
                資訊框1.標題 = msn1
                資訊框1.更新()
                self.第幾步 += 1
            # print(self.ai_data)
            # 文字框.print('已推演全部步數:' + str(len(self.ai_data))+ \
            #           '當前步數:' + str(len(self.ai_data[-1])))
            msn2 = '當前步數:' + str(self.總計數[self.第幾步][0]) \
                   + ' / ' + str(self.總計數[self.第幾步][1])
            # msn3 = '總數:' + str(累計)\
            #        + ' / ' + str(self.總計數[9][2])
            msn3 = '總數:' + str(self.總計數[self.第幾步][0]+self.總計數[self.第幾步-1][2])\
                   + ' / ' + str(self.總計數[9][2])
            資訊框2.標題 = msn2
            資訊框2.更新()
            資訊框3.標題 = msn3
            資訊框3.更新()
            # 文字框.print(str(總計數[self.第幾步]))
            pass

        開始按鈕.更改狀態(1)
        文字框.print('推演結束!')

起初用列表儲存遞推結果,覺得浪費記憶體,改用指標,但沒有明顯提升效能。看來已經是單執行緒極限了。
推演生成資料庫23M

AI演算法及綜合排序:

class AI9():
    # 資料結構:
    # ID,上級ID,層數,下級總數,已推下級數,
    # X,Y,結果1勝2平,勝利數,平局數,
    # 失敗數,勝率,和率,負率

    def __init__(self):
        self.ai_data = []
        self.第幾層 = 1
        self.走法數 = 0
        self.the_id = 0
        self.up_id = 0
        self.AI方='黑方'
        self.step_list=[]
        self.conn=sqlite3.connect('試驗資料.db')
        # rs=conn.cursor()
        # conn.commit()
        # conn.close()

    def 響應(self):
        def tolist(onelist):
            newlist = []
            for i in onelist:
                newlist.append(list(i))
            return newlist

        # 狀態=game.遊戲狀態標緻

        狀態, 落子方, 第幾步=game.裁判.遊戲狀態()
        if 狀態 == '勝利':
            奇步id=[]
            偶步id=[]
            for i in self.step_list:
                if i[2]%2==1:
                    奇步id.append(i[0])
                else:
                    偶步id.append(i[0])

            if 第幾步%2==1:
                sqlstr='update ai_data set 勝經驗=勝經驗+1 where id in %s'%str(tuple(偶步id))
                self.conn.execute(sqlstr)
                sqlstr='update ai_data set 負經驗=負經驗+1 where id in %s'%str(tuple(奇步id))
                self.conn.execute(sqlstr)
                self.conn.commit()
            else:
                sqlstr='update ai_data set 勝經驗=勝經驗+1 where id in %s'%str(tuple(奇步id))
                print(sqlstr)
                self.conn.execute(sqlstr)
                sqlstr='update ai_data set 負經驗=負經驗+1 where id in %s'%str(tuple(偶步id))
                self.conn.execute(sqlstr)
                self.conn.commit()

            if 落子方=='白方':
                if self.AI方=='白方':
                    msn='人類獲勝'
                else:
                    msn='AI獲勝'
            else:
                if self.AI方 == '黑方':
                    msn = '人類獲勝'
                else:
                    msn = 'AI獲勝'
            game.資訊框1.標題 = msn
            game.資訊框1.更新()
            return

        if 狀態 == '平局':
            平局id = []
            for i in self.step_list:
                平局id.append(i[0])
            sqlstr='update ai_data set 平經驗=平經驗+1 where id in %s'%str(tuple(平局id))
            self.conn.execute(sqlstr)
            self.conn.commit()

        if 狀態!= '等待' or 落子方!=self.AI方:
            return

        落子鏈 = game.裁判.返回儲存()

        # print('落子鏈',落子鏈)
        # print('step_list', self.step_list)
        最佳位置=[]

        if len(self.step_list)+1==len(落子鏈):
            #找對方落子id
            urlstr='''
select * from ai_data where 
uid=%d 
and 層數=%d 
and x=%d 
and y=%d '''\
                   %(self.up_id,第幾步-1,落子鏈[-1][1],落子鏈[-1][0])
            # print(urlstr)
            走法 = list(self.conn.execute(urlstr))
            self.step_list.append(走法[0])
            self.up_id = 走法[0][0]
            # print(走法)

        urlstr= 'select * from ai_data where uid=%d and 層數=%d' % (self.up_id, 第幾步)
        走法all=tolist(self.conn.execute(urlstr))
        走法=self.綜合排序(走法all)
        最佳位置 = 走法[0]
        if self.AI方==game.落子方:
            self.step_list.append(最佳位置)
            self.up_id=最佳位置[0]
            game.落子(最佳位置[5],最佳位置[6])
            # print('走法[0]',走法[0])
        # print(self.step_list)
        # print(self.up_id)

    def 綜合排序(self,onelist):
        def 加序號列(onelist, 權重=1):
            newlist = onelist
            nob = 權重
            for i in newlist:
                i.append(nob)
                nob += 1
            return newlist

        def 刪除序號列(onelist, nob):
            for i in onelist:
                del i[-nob:]
            return onelist

        正順序 = True
        負順序 = False

        勝負結果 = sorted(onelist, key=lambda mylist: mylist[8], reverse=正順序)
        for i in 勝負結果:
            if i[8]==9:
                return 勝負結果
                break
        # 勝負結果 = 加序號列(勝負結果, 權重=-20)
        # self.printlist('勝負結果',勝負結果)

        排序勝算 = sorted(
            onelist,
            key=lambda mylist: mylist[9] - mylist[11],
            reverse=正順序)
        排序勝算 = 加序號列(排序勝算, 權重=1)

        排序勝率 = sorted(
            onelist,
            key=lambda mylist: mylist[9] / (1+mylist[9] + mylist[10] + mylist[11]),
            reverse=正順序)
        排序勝率 = 加序號列(排序勝率)

        排序勝經驗 = sorted(
            onelist,
            key=lambda mylist: mylist[12] - mylist[14],
            reverse=正順序)
        排序勝經驗 = 加序號列(排序勝經驗)

        排序負數 = sorted(onelist, key=lambda mylist: mylist[11], reverse=負順序)
        排序負數 = 加序號列(排序負數)

        排序負經驗 = sorted(onelist, key=lambda mylist: mylist[14], reverse=負順序)
        排序負經驗 = 加序號列(排序負經驗)

        總序 = sorted(
            onelist,
            key=lambda mylist: mylist[-1] + mylist[-2] + mylist[-3] + mylist[-4]*2 + mylist[-5]*2,
            reverse=負順序)
        self.printlist('總序',總序)
        刪除序號列(總序, 5)

        return 總序

程式原始碼及所有資料與大家分享
https://pan.baidu.com/s/1oo-UCfalKJ52joME0TAzfA

有興趣的朋友可加好友,共同研究。qq:3152506