• x01.weiqi.14: 跨平台 python 实现


    大多数时间,都在使用 deepin 系统,原来的 WPF 实现似乎越来越遥远了。这几天在家学习了一下 tkinter,顺便予以重写。

    内在逻辑是一样的,就不重复,但具体实现层面,如图像不能改变大小等不一而足。由于 AI 出现,再去探讨怎么落子已变得毫无意义,所以只实现了最基本的吃子,打劫,倒扑,悔棋功能。

    main.py 代码如下:

    import os, sys, enum 
    import tkinter as tk 
    import tkinter.filedialog as fdlg
    import tkinter.messagebox as mbox 
    from core  import Board, StepBoard, Sgf, R 
    
    CurrDir = os.path.dirname(__file__)
    sys.path.append(CurrDir)
    
    class App(tk.Tk):
        def __init__(self):
            super().__init__()
    
            menu = tk.Menu(self)
            
            weiqi_menu = tk.Menu(menu, tearoff=0)
            weiqi_menu.add_command(label='开始', command=self.start)
            weiqi_menu.add_command(label='加载SGF', command=self.load)
            weiqi_menu.add_command(label='保存SGF', command=self.save)
            weiqi_menu.add_command(label='退出', command=self.destroy)
            menu.add_cascade(label='围棋', menu=weiqi_menu)
    
            help_menu = tk.Menu(menu, tearoff=0)
            help_menu.add_command(label='测试', command=self.test)
            help_menu.add_command(label='关于', command=self.about)
            menu.add_cascade(label="帮助", menu=help_menu)
    
            self.config(menu=menu)
    
            self.boards = [Board(self), StepBoard(self)]
    
            self.board = self.boards[0]
            self.board.pack()
    
            self.x = (self.winfo_screenwidth()-self.board.w)//2
            self.set_geometry(self.board.w,self.board.h,self.x)
    
            self.title('x01.weiqi')
            self.configure(background=R.BgColor)
    
        def set_geometry(self,w=800,h=600,x=300,y=20):
            self.geometry('{}x{}+{}+{}'.format(w,h,x,y))
    
        def test(self):
            self.board.test_steps()
    
        def about(self):
            mbox.showinfo("关于 x01.weiqi", "为跨平台而采用 python 实现的围棋程序 x01.weiqi, 版权属于x01(黄雄)所有。")
    
        def start(self):
            self.board.back_all()
            self.board.pack_forget()
            self.board = self.boards[0]
            self.board.pack()
            self.set_geometry(self.board.w,self.board.h,self.x)
    
        def load(self):
            self.board.back_all()
            self.board.pack_forget()
            self.board = self.boards[1]
            self.board.pack()
            types = (('SGF files', '*.sgf'), ('All files', '*.*'))
            filename = fdlg.askopenfilename(title='打开棋谱', filetypes=types)
            if filename: self.board.load(filename)
            self.set_geometry(self.board.w,self.board.h,self.x)
    
        def save(self):
            steps = self.board.wqcore.SgfSteps
            sgf = Sgf()
            types = (('SGF files', '*.sgf'), ('All files', '*.*'))
            filename = fdlg.asksaveasfilename(title='保存棋谱', filetypes=types)
            if filename:
                sgf.Save(steps, filename)
                mbox.showinfo('保存棋谱','[' + filename + '] 保存成功')
    
    
    if __name__ == "__main__":
        App().mainloop()
    main.py

    core.py 代码如下:

    import os 
    import tkinter as tk 
    import tkinter.ttk as ttk 
    
    CurrDir = os.path.dirname(__file__)
    
    class Board(tk.Frame):
        def __init__(self, master):
            super().__init__(master)
            self.w = self.h = R.StoneSize * R.CellNumber+10
            self.offset = R.StoneSize/2+5
            self.canvas = tk.Canvas(self, bg=R.BgColor,width=self.w,height=self.h)
            self.canvas.bind(R.ButtonLeft, self.next_one)
            self.canvas.bind(R.ButtonRight, self.back_one)
            self.canvas.pack()
            self.draw_lines()
            self.draw_stars()
            
            self.black_stone = tk.PhotoImage(file=os.path.join(CurrDir,'res/black-{}.png'.format(R.StoneSize)))
            self.white_stone = tk.PhotoImage(file=os.path.join(CurrDir,'res/white-{}.png'.format(R.StoneSize)))
            self.isBlack = True 
            self.poses = []
            self.stepCount = 0
            self.wqcore = WqCore()
            self.once = Record() # 打劫
            self.deads = {} # 悔棋
    
            self.configure(background=R.BgColor)
    
        def draw_lines(self):
            for i in range(R.CellNumber):
                self.canvas.create_line(self.offset,i*R.StoneSize + self.offset,self.w-self.offset,i*R.StoneSize+self.offset)
                self.canvas.create_line(i*R.StoneSize+self.offset,self.offset,i*R.StoneSize+self.offset,self.h-self.offset)
    
        def draw_stars(self):
            stars = [(3,3), (3,9), (3,15),(9,3,),(9,9),(9,15),(15,3),(15,9),(15,15)]
            r = 3
            for i in range(R.StarNumber):
                x,y = self.offset + stars[i][0]*R.StoneSize,self.offset + stars[i][1]*R.StoneSize
                self.canvas.create_oval(x-r, y-r, x+r, y+r, fill="black")
    
        def next_one(self, e):
            x,y = e.x//R.StoneSize, e.y // R.StoneSize
            color = 1 if self.isBlack else -1
            self.draw_stone((x,y), color)
            self.kill()
    
        def draw_stone(self, pos, color):
            if color==0: return
            record = self.wqcore.GetRecord(pos)  
            if record is None or record.color != 0: return      
            x,y = pos
            self.stepCount += 1
            self.poses.append(pos)
            self.wqcore.Steps.append(Record(row=y,col=x,count=self.stepCount, color=color))
            self.wqcore.SgfSteps.append(Record(y,x,self.stepCount,color))
            self.wqcore.UpdateRecords()
            stone = self.black_stone if color == 1 else self.white_stone
            self.canvas.create_image(self.offset + x*R.StoneSize, self.offset + y*R.StoneSize, image=stone)
            self.isBlack = not self.isBlack
    
        def kill(self):
            deads = self.wqcore.KillOther()
            steps = self.wqcore.Steps[:]
            if deads is not None:
                if len(deads) == 1:
                    col,row = deads[0]
                    if self.once.count == self.stepCount - 1 and (col,row) in self.wqcore.LinkPoses((self.once.col,self.once.row)):
                        self.back_one(None)
                        return
                    self.once = Record(row, col, self.stepCount,0)
                dead_records = [ self.wqcore.GetRecord(d) for d in deads]
                self.deads[self.stepCount] = [Record(d.row,d.col,d.count,-self.wqcore.Current().color) for d in dead_records]
                for d in deads:
                    r = self.wqcore.GetRecord(d)
                    r = Record(r.row,r.col,r.count, -self.wqcore.Current().color)
                    self.delete_stone(d)
                    for i,s in enumerate(steps):
                        if (s.col,s.row) == d and r.count == s.count:
                            self.wqcore.Steps[i].color = 0
            else:
                deads = self.wqcore.KillSelf()
                if deads is not None:
                    dead_records = [ self.wqcore.GetRecord(d) for d in deads]
                    self.deads[self.stepCount] = [Record(d.row,d.col,d.count,self.wqcore.Current().color) for d in dead_records]
                    for d in deads:
                        r = self.wqcore.GetRecord(d)
                        r = Record(r.row,r.col,r.count,self.wqcore.Current().color)
                        self.delete_stone(d)
                        for i,s in enumerate(steps):
                            if (s.col,s.row) == d and r.count == s.count:
                                self.wqcore.Steps[i].color = 0
            self.wqcore.DeadPoses.clear()
            self.wqcore.UpdateRecords()
    
        def test_steps(self):
            print(self.wqcore.SgfSteps)
    
        def back_all(self):
            for step in self.wqcore.Steps[:]:
                self.back_one(None)
    
        def back_one(self, e):
            if len(self.poses) == 0: return
            pos = self.poses.pop()
            self.delete_stone(pos)
            self.show_deads()
            p = self.wqcore.Steps.pop()
            self.wqcore.SgfSteps.pop()
            for i, r in enumerate(self.wqcore.Records[:]):
                if (p.col, p.row) == (r.col,r.row):
                    self.wqcore.Records[i].count = -1
                    self.wqcore.Records[i].color = 0 
            self.isBlack = not self.isBlack
            self.stepCount -= 1
            
            
        def last_deads(self):
            if self.stepCount in self.deads.keys():
                deads = self.deads[self.stepCount][:]
                del self.deads[self.stepCount]
                return deads
            return None  
    
        def get_deads(self):
            deads = self.last_deads()
            if deads is None: return None 
            self.wqcore.GetDeadBlocks(deads)
            result = [r for r in deads if (r.col,r.row) in self.wqcore.DeadPoses]
            self.wqcore.DeadPoses.clear()
            return result[:]
        
        def show_deads(self):
            deads = self.get_deads()
            if deads is None: return
            for d in deads:
                for i, s in enumerate(self.wqcore.Steps[:]):
                    if s.count == d.count:
                        self.wqcore.Steps[i].color = d.color 
                stone = self.black_stone if d.color == 1 else self.white_stone
                self.canvas.create_image(self.offset + d.col*R.StoneSize, self.offset + d.row*R.StoneSize, image=stone)
            self.wqcore.DeadPoses.clear()
            self.wqcore.UpdateRecords()
                
        def delete_stone(self, pos):
            r = self.wqcore.GetRecord(pos)
            if r is None or r.color == 0: return
            col,row = pos 
            x,y = self.offset + col*R.StoneSize, self.offset + row*R.StoneSize
            stone = self.canvas.find_closest(x,y)
            if stone:
                self.canvas.delete(stone)
    
    class WqCore:
        def __init__(self):
            self.Steps = [] # 棋步
            self.SgfSteps = []
            self.Records = [Record(r,c) for r in range(0,19) for c in range(0,19)] # 棋谱
            self.DeadPoses = []
    
        def UpdateRecords(self):
            records = self.Records[:]
            for i,r in enumerate(records):
                for s in self.Steps:
                    if (s.col,s.row) == (r.col,r.row):
                        self.Records[i] = s 
    
        def Current(self):
            if len(self.Steps) == 0: return None 
            else: return self.Steps[-1]
    
        def KillOther(self):
            c = self.Current()
            if c is None: return None 
            poses = self.LinkPoses((c.col,c.row))
            records = [r for r in self.Records if (r.col,r.row) in poses and r.color != c.color and r.color != 0]
            result = []
            for temp in records:
                self.DeadPoses.clear()
                deads = self.GetDeadPoses((temp.col, temp.row))
                if deads is not None: [result.append(d) for d in deads]
            return None if len(result) == 0 else result[:]
    
        def KillSelf(self):
            c = self.Current()
            if c is None: return None 
            poses = self.LinkPoses((c.col,c.row))
            records = [r for r in self.Records if (r.col,r.row) in poses and r.color == c.color and r.color != 0]
            for temp in records:
                self.DeadPoses.clear()
                deads = self.GetDeadPoses((temp.col, temp.row))
                if deads is not None: return deads 
            return None 
    
        def GetRecord(self, pos):
            for r in self.Records:
                if (r.col, r.row) == pos: return r 
            return None 
    
        def GetDeadPoses(self, pos):
            record = self.GetRecord(pos)
            self.DeadPoses.append(pos)
            poses = set(self.LinkPoses(pos)).difference(set(self.DeadPoses))
            records = [r for r in self.Records if (r.col,r.row) in poses]
            result = []
            for t in records:
                if t.color == 0: 
                    self.DeadPoses.clear()
                    return None 
                elif t.color == record.color:
                    deads = self.GetDeadPoses((t.col,t.row))
                    if deads is not None: continue
                    else: return None
            return self.DeadPoses
    
        def GetDeadBlocks(self, deads):
            self.DeadPoses.clear()
            curr = self.Current()
            col,row,color = curr.col, curr.row,curr.color 
            poses = self.LinkPoses((col,row))
            for pos in poses:
                for d in deads:
                    if (d.col,d.row) == pos and d.color == -color:
                        self.GetDeadBlock(d,deads)
    
        def GetDeadBlock(self, record, deads):
            col,row,color = record.col,record.row,record.color
            self.DeadPoses.append((col,row))
            links = set(self.LinkPoses((col,row))).difference(set(self.DeadPoses))
            poses = [(r.col,r.row) for r in deads if (r.col,r.row) in links and r.color == color]
            for x,y in poses:
                r = self.GetRecord((x,y))
                r.color = color 
                self.GetDeadBlock(r, deads)
    
        def IsValid(self, pos):
            x,y = pos 
            if 0 <= x < 19 and 0 <= y < 19: return True
            else: return False
    
        def LinkPoses(self, pos):
            poses = []
            x,y = pos
            for i in range(-1,2):
                for j in range(-1,2):
                    if i==j or i==-j: continue
                    if self.IsValid(pos):
                        poses.append((x+j,y+i))
            poses.append(pos)
            return poses
    
        def RoundPoses(self, pos):
            poses = []
            x,y = pos
            for i in range(-1,2):
                for j in range(-1,2):
                    if self.IsValid(pos):
                        poses.append((x+j,y+i))
            return poses
    
    class Record:
        def __init__(self, row=-1,col=-1,count=-1,color=0):
            self.row = row 
            self.col = col
            self.count = count 
            self.color = color  # black:1,white:-1,empty=0
    
        def __repr__(self):
            return 'Record({},{},{},{})'.format(self.row,self.col,self.count,self.color)
    
    class R:
        # stone and board
        StoneSize = 32
        CellNumber = 19
        StarNumber = 9
        BgColor = '#ec5'
    
        # bind event names
        ButtonLeft = '<Button-1>'
        ButtonRight = '<Button-3>'
    
    class Sgf:
        # # 根属性
        # CA = 'CA'   # 字符集
        # FF = 'FF'   # 文件格式(1-4)
        # GM = 'GM'   # 对局类别:1 围棋, 3 国际象棋,4 五子棋,7 中国象棋
        # SZ = 'SZ'   # 棋盘大小
        # AP = 'AP'   # 应用软件
    
        # # 对局信息
        # GN = 'GN'   # 棋谱名称
        # GC = 'GC'   # 棋谱备注
        # PB ='PB'    # 黑方
        # PW = 'PW'   # 白方
        # BR = 'BR'   # 黑方段位
        # WR = 'WR'   # 白方段位
        # RE = 'RE'   # 结果:0 和棋,B+3 黑胜3目,B+R 黑中盘胜
        # KM = 'KM'   # 贴目
        # HA = 'HA'   # 让子
        # TM = 'TM'   # 对局时限
        # DT = 'DT'   # 日期
        # SO = 'SO'   # 来源
        # OT = 'OT'   # 读秒方式
        # RU = 'RU'   # 规则
        # RM = 'RM'
        # US = 'US'   # 编写者
        # CP = 'CP'   # 版权
        # AN = 'AN'   # 注解
        # EV = 'EV'   # 赛事
        # RO = 'RO'   # 回合
        # PC = 'PC'   # 地点
    
        # # 走子
        # B = 'B'     # 黑走子
        # W = 'W'     # 白走子
    
        def __init__(self, pb='black', pw='white',dt='2020-3-11', gn='Test'):
            self.Begin = '(;GM[1]PB[{}]PW[{}]BR[2D]WR[2D]RE[]KM[7.5]HA[0]TM[{}]DT[600]GN[{}]SO[x01.weiqi]OT[3/0.5]SZ[19]FF[4]CA[UTF-8]RU[zh]RM[]'.format(
                pb,pw,dt,gn
            )
            self.End = ')'
    
        def ToSgf(self, steps):
            body = ''
            a = ord('a')
            for step in steps:
                color = ';B[' if step.color == 1 else ';W['
                body += color + chr(a+step.col) + chr(a + step.row) + ']'
            return self.Begin+body+self.End
    
        def ToSteps(self, sgf):
            sgfs = sgf.split(';')
            sgfs = sgfs[2:-1]
            count = 0
            steps = []
            a = ord('a')
            for s in sgfs:
                color = 1 if s[0:1] == 'B' else -1
                col = ord(s[2:3]) - a 
                row = ord(s[3:4]) - a
                count += 1
                steps.append(Record(row,col,count,color))
            return steps
    
        def Load(self, filename):
            sgf = ''
            with open(filename) as f:
                for line in f:
                    line = line.strip()
                    sgf += line 
            return self.ToSteps(sgf)
    
        def Save(self, steps, filename):
            with open(filename, 'w') as fd:
                sgf = self.ToSgf(steps)
                fd.write(sgf)
    
    class StepBoard(Board):
        def __init__(self, master):
            super().__init__(master)
            self.sgf = Sgf()
            self.sgf_steps = None 
            self.creat_operatebar()
            
        def next_one(self, e):
            if self.sgf_steps is None: return
            step = self.sgf_steps[self.stepCount]
            self.draw_stone((step.col,step.row), step.color)
            self.kill()
    
        def load(self, filename):
            self.sgf_steps = self.sgf.Load(filename)
    
        def creat_operatebar(self):
            bar = tk.Frame(self, height=40, width=self.w, bg=R.BgColor)
            btnStart = tk.Button(bar, text='|<', command=lambda : self.back_all(), bg=R.BgColor)
            btnPrevFive = tk.Button(bar,  text='<<', command=lambda : [self.back_one(None) for i in range(5)],bg=R.BgColor)
            btnPrev = tk.Button(bar, text='<', command=lambda : self.back_one(None),bg=R.BgColor)
            btnNext = tk.Button(bar,text='>', command=lambda : self.next_one(None),bg=R.BgColor)
            btnNextFive = tk.Button(bar,text='>>', command=lambda : [self.next_one(None) for i in range(5)],bg=R.BgColor)
            btnEnd = tk.Button(bar,text='>|', command=lambda : [self.next_one(None) for i in range(len(self.sgf_steps)-self.stepCount)],bg=R.BgColor)
            btnStart.pack(side=tk.LEFT, padx=6)
            btnPrevFive.pack(side=tk.LEFT, padx=6)
            btnPrev.pack(side=tk.LEFT, padx=6)
            btnNext.pack(side=tk.LEFT, padx=6)
            btnNextFive.pack(side=tk.LEFT, padx=6)
            btnEnd.pack(side=tk.LEFT, padx=6)
            bar.pack()
            self.h += 40
    
    if __name__ == "__main__":
        print(range(0))
        pass 
    core.py

    代码在 x01.lab/py/game/weiqi 下,链接:https://gitee.com/chinax01/x01.lab

  • 相关阅读:
    《构建之法(第三版)》第一章学习总结
    20189220余超 2019年密码与安全新技术讲座-课程总结报告
    学号20189220 2018-2019-2 《密码与安全新技术专题》第六周作业
    学号20189220余超 2018-2019-2 《密码与安全新技术专题》第七周作业
    20189200余超 2018-2019-2 移动平台应用开发实践作项目代码分析
    2018-2019-2学号20189220余超《移动平台应用程序开发实践》课程总结
    20189200余超 2018-2019-2 移动平台应用开发实践第十二周作业
    20189220余超 团队博客——阅读软件app
    20189200余超 2018-2019-2 移动平台应用开发实践第十一周作业
    20189200余超 2018-2019-2 移动平台应用开发实践第十周作业
  • 原文地址:https://www.cnblogs.com/china_x01/p/12456382.html
Copyright © 2020-2023  润新知