You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

584 lines
23 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

from tkinter import *
# ttk覆盖tkinter部分对象ttk对tkinter进行了优化
from tkinter.ttk import *
# 深拷贝时需要用到copy模块
import copy
import time
import threading
import tkinter.messagebox
# 围棋应用对象定义
class Application(Tk):
# 初始化棋盘,默认九路棋盘
def __init__(self,my_mode_num=9):
Tk.__init__(self)
# 模式九路棋9十三路棋13十九路棋19
self.mode_num=my_mode_num
# 窗口尺寸设置默认1.8
self.size=1.6
# 棋盘每格的边长
self.dd=360*self.size/(self.mode_num-1)
# 相对九路棋盘的矫正比例
self.p=1 if self.mode_num==9 else (2/3 if self.mode_num==13 else 4/9)
# 定义棋盘阵列,超过边界:-1无子0黑棋1白棋2
self.positions=[[0 for i in range(self.mode_num+2)] for i in range(self.mode_num+2)]
# 初始化棋盘,所有超过边界的值置-1
for m in range(self.mode_num+2):
for n in range(self.mode_num+2):
if (m*n==0 or m==self.mode_num+1 or n==self.mode_num+1):
self.positions[m][n]=-1
# 拷贝三份棋盘“快照”,悔棋和判断“打劫”时需要作参考
self.last_3_positions=copy.deepcopy(self.positions)
self.last_2_positions=copy.deepcopy(self.positions)
self.last_1_positions=copy.deepcopy(self.positions)
# 记录鼠标经过的地方用于显示shadow时
self.cross_last=None
# 当前轮到的玩家01执黑先行
self.present=0
# 初始停止运行,点击“开始游戏”运行游戏
self.stop=True
# 悔棋次数次数大于0才可悔棋初始置0初始不能悔棋悔棋后置0下棋或弃手时恢复为1以禁止连续悔棋
self.regretchance=0
# 图片资源,存放在当前目录下的/Pictures/中
self.photogo=PhotoImage(file = "./Pictures/go.png")
self.photovs=PhotoImage(file = "./Pictures/vs.png")
self.photoW=PhotoImage(file = "./Pictures/W.png")
self.photoB=PhotoImage(file = "./Pictures/B.png")
self.photoJ=PhotoImage(file = "./Pictures/J.png")
self.photoY=PhotoImage(file = "./Pictures/Y.png")
self.photoBD=PhotoImage(file = "./Pictures/"+"BD"+"-"+str(self.mode_num)+".png")
self.photoWD=PhotoImage(file = "./Pictures/"+"WD"+"-"+str(self.mode_num)+".png")
self.photoBU=PhotoImage(file = "./Pictures/"+"BU"+"-"+str(self.mode_num)+".png")
self.photoWU=PhotoImage(file = "./Pictures/"+"WU"+"-"+str(self.mode_num)+".png")
# 用于黑白棋子图片切换的列表
self.photoWBU_list=[self.photoBU,self.photoWU]
self.photoWBD_list=[self.photoBD,self.photoWD]
# 窗口大小
self.geometry(str(int(600*self.size))+'x'+str(int(400*self.size)))
# 画布控件,作为容器
self.canvas_bottom=Canvas(self,bg='#957',bd=0,width=600*self.size,height=400*self.size)
self.canvas_bottom.place(x=0,y=0)
# 几个功能按钮
self.startButton=Button(self,text='开始游戏',command=self.start)
self.startButton.place(x=480*self.size,y=200*self.size)
self.passmeButton=Button(self,text='弃一手',command=self.passme)
self.passmeButton.place(x=480*self.size,y=225*self.size)
self.regretButton=Button(self,text='悔棋',command=self.regret)
self.regretButton.place(x=480*self.size,y=250*self.size)
# 初始悔棋按钮禁用
self.regretButton['state']=DISABLED
self.replayButton=Button(self,text='重新开始',command=self.reload)
self.replayButton.place(x=480*self.size,y=275*self.size)
self.newGameButton1=Button(self,text=('十三' if self.mode_num==9 else '')+'路棋',command=self.newGame1)
self.newGameButton1.place(x=480*self.size,y=300*self.size)
self.newGameButton2=Button(self,text=('十三' if self.mode_num==19 else '十九')+'路棋',command=self.newGame2)
self.newGameButton2.place(x=480*self.size,y=325*self.size)
self.newGameButton3=Button(self,text='九路飞刀',command=self.newGame3)
self.newGameButton3.place(x=480*self.size,y=350*self.size)
self.quitButton=Button(self,text='退出游戏',command=self.quit)
self.quitButton.place(x=480*self.size,y=375*self.size)
self.title("倒计时示例")
self.l1 =Label(self)
# 画棋盘,填充颜色
self.canvas_bottom.create_rectangle(0*self.size,0*self.size,400*self.size,400*self.size,fill='#FFA500')
# 刻画棋盘线及九个点
# 先画外框粗线
self.canvas_bottom.create_rectangle(20*self.size,20*self.size,380*self.size,380*self.size,width=3)
# 棋盘上的九个定位点,以中点为模型,移动位置,以作出其余八个点
for m in [-1,0,1]:
for n in [-1,0,1]:
self.oringinal=self.canvas_bottom.create_oval(200*self.size-self.size*2,200*self.size-self.size*2,
200*self.size+self.size*2,200*self.size+self.size*2,fill='#000')
self.canvas_bottom.move(self.oringinal,m*self.dd*(2 if self.mode_num==9 else (3 if self.mode_num==13 else 6)),
n*self.dd*(2 if self.mode_num==9 else (3 if self.mode_num==13 else 6)))
# 画中间的线条
for i in range(1,self.mode_num-1):
self.canvas_bottom.create_line(20*self.size,20*self.size+i*self.dd,380*self.size,20*self.size+i*self.dd,width=2)
self.canvas_bottom.create_line(20*self.size+i*self.dd,20*self.size,20*self.size+i*self.dd,380*self.size,width=2)
for i in range(1,self.mode_num+1):
self.canvas_bottom.create_text(10*self.size,20*self.size+(i-1)*self.dd, text=str(self.mode_num+1-i))
self.canvas_bottom.create_text(20*self.size+(i-1)*self.dd,390*self.size, text=chr(64 + i))
#放置左侧欢迎图片
self.go=self.canvas_bottom.create_image(120*self.size, 120*self.size,image=self.photogo)
self.welcome_text = self.canvas_bottom.create_text(
120*self.size, 120*self.size,
text="Welcome to Go!\n姓名: 唐鹏杰\n班级: 计科2105\n学号: 21412030525\n指导老师: 肖 哲",
font=("Arial", 30,"bold"),
fill="#00BFFF",
anchor="center",
)
# 放置右侧初始图片
self.vs=self.canvas_bottom.create_image(500*self.size, 150*self.size,image=self.photovs)
self.pW=self.canvas_bottom.create_image(500*self.size+30, 65*self.size,image=self.photoW)
self.pJ=self.canvas_bottom.create_image(500*self.size-109, 150*self.size,image=self.photoJ)
self.pB=self.canvas_bottom.create_image(500*self.size+8, 65*self.size,image=self.photoB)
self.pY=self.canvas_bottom.create_image(500*self.size+129, 150*self.size,image=self.photoY)
# 每张图片都添加image标签方便reload函数删除图片
self.canvas_bottom.addtag_withtag('image',self.pW)
self.canvas_bottom.addtag_withtag('image',self.pB)
self.canvas_bottom.addtag_withtag('image',self.pJ)
self.canvas_bottom.addtag_withtag('image',self.pY)
# 鼠标移动时调用shadow函数显示随鼠标移动的棋子
self.canvas_bottom.bind('<Motion>',self.shadow)
# 鼠标左键单击时调用getdown函数放下棋子
self.canvas_bottom.bind('<Button-1>',self.getDown)
# 设置退出快捷键<Ctrl>+<D>,快速退出游戏
self.bind('<Control-KeyPress-d>',self.keyboardQuit)
#欢迎界面显示文字
def welcome(self):
self.l2 =Label(self)
self.l2.config(text=f'白棋剩余')
self.l2.place(x=70*self.size, y=75*self.size) # 设置text文字显示位置
# 开始游戏函数,点击“开始游戏”时调用
def start(self):
# 删除右侧和欢迎界面图案
self.canvas_bottom.delete(self.pW)
self.canvas_bottom.delete(self.pB)
self.canvas_bottom.delete(self.pJ)
self.canvas_bottom.delete(self.pY)
self.canvas_bottom.delete(self.go)
self.canvas_bottom.delete(self.welcome_text)
# 利用右侧图案提示开始时谁先落子
if self.present==0:
self.create_pB()
self.del_pW()
else:
self.create_pW()
self.del_pB()
# 开始标志解除stop
self.stop=None
# 放弃一手函数,跳过落子环节
def passme(self):
# 悔棋恢复
if not self.regretchance==1:
self.regretchance+=1
else:
self.regretButton['state']=NORMAL
# 拷贝棋盘状态,记录前三次棋局
self.last_3_positions=copy.deepcopy(self.last_2_positions)
self.last_2_positions=copy.deepcopy(self.last_1_positions)
self.last_1_positions=copy.deepcopy(self.positions)
self.canvas_bottom.delete('image_added_sign')
# 轮到下一玩家
if self.present==0:
self.create_pW()
self.del_pB()
self.present=1
else:
self.create_pB()
self.del_pW()
self.present=0
# 悔棋函数,可悔棋一回合,下两回合不可悔棋
def regret(self):
# 判定是否可以悔棋,以前第三盘棋局复原棋盘
if self.regretchance==1:
self.regretchance=0
self.regretButton['state']=DISABLED
list_of_b=[]
list_of_w=[]
self.canvas_bottom.delete('image')
if self.present==0:
self.create_pB()
else:
self.create_pW()
for m in range(1,self.mode_num+1):
for n in range(1,self.mode_num+1):
self.positions[m][n]=0
for m in range(len(self.last_3_positions)):
for n in range(len(self.last_3_positions[m])):
if self.last_3_positions[m][n]==1:
list_of_b+=[[n,m]]
elif self.last_3_positions[m][n]==2:
list_of_w+=[[n,m]]
self.recover(list_of_b,0)
self.recover(list_of_w,1)
self.last_1_positions=copy.deepcopy(self.last_3_positions)
for m in range(1,self.mode_num+1):
for n in range(1,self.mode_num+1):
self.last_2_positions[m][n]=0
self.last_3_positions[m][n]=0
# 重新加载函数,删除图片,序列归零,设置一些初始参数,点击“重新开始”时调用
def reload(self):
if self.stop==1:
self.stop=0
self.canvas_bottom.delete('image')
self.regretchance=0
self.present=0
self.create_pB()
for m in range(1,self.mode_num+1):
for n in range(1,self.mode_num+1):
self.positions[m][n]=0
self.last_3_positions[m][n]=0
self.last_2_positions[m][n]=0
self.last_1_positions[m][n]=0
# 以下四个函数实现了右侧太极图的动态创建与删除
#白棋图标创建
def create_pW(self):
self.stop_countdown() # 停止当前计时
self.start_countdown(self.countdown_white)
self.pW=self.canvas_bottom.create_image(500*self.size+30, 65*self.size,image=self.photoW)
self.canvas_bottom.addtag_withtag('image',self.pW)
self.pJ=self.canvas_bottom.create_image(500*self.size-109, 65*self.size,image=self.photoJ)
self.canvas_bottom.addtag_withtag('image',self.pJ)
self.steps_label = Label(self,text="执白后行",background='#957')
self.steps_label.place(x=430*self.size, y=125*self.size)
#黑棋图标创建
def create_pB(self):
self.stop_countdown() # 停止当前计时
self.start_countdown(self.countdown_black)
self.pB=self.canvas_bottom.create_image(500*self.size+8, 65*self.size,image=self.photoB)
self.canvas_bottom.addtag_withtag('image',self.pB)
self.pY=self.canvas_bottom.create_image(500*self.size+129, 65*self.size,image=self.photoY)
self.canvas_bottom.addtag_withtag('image',self.pY)
self.steps_label = Label(self,text="执黑先行",background='#957')
self.steps_label.place(x=530*self.size, y=125*self.size)
def del_pW(self):
self.canvas_bottom.delete(self.pW)
self.canvas_bottom.delete(self.pJ)
def del_pB(self):
self.canvas_bottom.delete(self.pB)
self.canvas_bottom.delete(self.pY)
#创建文本信息
'''def create_bb(self):
self.steps_label = Tk.Label(self, text="步数:0DEWQDEW")
self.steps_label.place(x=50*self.size+129, y=65*self.size)'''
#倒计时
'''def countdown(self):
remaining_time = 7200 # 120分钟对应的秒数
for i in range(remaining_time, 0, -1):
self.l1.config(text=f'剩余时间:{i // 60}分{i % 60}秒')
self.update()
time.sleep(1)'''
# 调用countdown函数开始倒计时
# 黑棋倒计时函数
def countdown_black(self, remaining_time=None):
if remaining_time is None:
remaining_time = 15 # 5分钟对应的秒数
self.present = 0 # 设置当前计时的玩家为黑棋
while remaining_time > 0:
if self.present != 0: # 如果当前计时的不是黑棋,退出循环
break
self.l1.config(text=f'黑棋剩余:{remaining_time // 60}{remaining_time % 60}')
self.l1.place(x=520*self.size, y=175*self.size) # 设置text文字显示位置
self.update()
time.sleep(1)
remaining_time -= 1
if self.present == 0: # 如果当前计时的是黑棋
self.game_over("黑棋超时,白棋获胜")
# 白棋倒计时函数
def countdown_white(self, remaining_time=None):
if remaining_time is None:
remaining_time = 15 # 5分钟对应的秒数
self.present = 1 # 设置当前计时的玩家为白棋
while remaining_time > 0:
if self.present != 1:
break
self.l1.config(text=f'白棋剩余:{remaining_time // 60}{remaining_time % 60}')
self.l1.place(x=410*self.size, y=175*self.size) # 设置text文字显示位置
self.l1.update_idletasks() # 强制更新Label组件的显示
time.sleep(1)
remaining_time -= 1
if self.present == 1: # 如果当前计时的是白棋
self.game_over("白棋超时,黑棋获胜")
# 创建新线程执行倒计时函数
def start_countdown(self,countdown_func):
countdown_thread = threading.Thread(target=countdown_func, daemon=True)
countdown_thread.start()
def stop_countdown(self):
# 停止计时
if self.present is not None:
self.present = None
def game_over(self, message):
# 游戏结束操作,例如显示游戏结束信息、停止倒计时、禁用游戏操作等
self.showwarningbox('超时!',message)
self.stop_countdown() # 停止倒计时
self.reload()
'''def switch_player(self):
if self.present ==0:
self.stop_countdown()
self.create_pB() # 切换到白棋并启动白棋计时
self.present = 1
if hasattr(self, 'remaining_time_black'): # 如果之前有记录黑棋剩余时间
self.start_countdown(self.countdown_black, self.remaining_time_black) # 继续黑棋倒计时
else:
self.stop_countdown()
self.create_pW() # 切换到黑棋并启动黑棋计时
self.present = 0
if hasattr(self, 'remaining_time_white'): # 如果之前有记录白棋剩余时间
self.start_countdown(self.countdown_white, self.remaining_time_white) # 继续白棋倒计时'''
# 显示鼠标移动下棋子的移动
def shadow(self,event):
if not self.stop:
# 找到最近格点,在当前位置靠近的格点出显示棋子图片,并删除上一位置的棋子图片
if (20*self.size<event.x<380*self.size) and (20*self.size<event.y<380*self.size):
dx=(event.x-20*self.size)%self.dd
dy=(event.y-20*self.size)%self.dd
self.cross=self.canvas_bottom.create_image(event.x-dx+round(dx/self.dd)*self.dd+22*self.p, event.y-dy+round(dy/self.dd)*self.dd-27*self.p,image=self.photoWBU_list[self.present])
self.canvas_bottom.addtag_withtag('image',self.cross)
if self.cross_last!=None:
self.canvas_bottom.delete(self.cross_last)
self.cross_last=self.cross
# 落子,并驱动玩家的轮流下棋行为
def getDown(self,event):
if not self.stop:
# 先找到最近格点
if (20*self.size-self.dd*0.4<event.x<self.dd*0.4+380*self.size) and (20*self.size-self.dd*0.4<event.y<self.dd*0.4+380*self.size):
dx=(event.x-20*self.size)%self.dd
dy=(event.y-20*self.size)%self.dd
x=int((event.x-20*self.size-dx)/self.dd+round(dx/self.dd)+1)
y=int((event.y-20*self.size-dy)/self.dd+round(dy/self.dd)+1)
# 判断位置是否已经被占据
if self.positions[y][x]==0:
# 未被占据,则尝试占据,获得占据后能杀死的棋子列表
self.positions[y][x]=self.present+1
self.image_added=self.canvas_bottom.create_image(event.x-dx+round(dx/self.dd)*self.dd+4*self.p, event.y-dy+round(dy/self.dd)*self.dd-5*self.p,image=self.photoWBD_list[self.present])
self.canvas_bottom.addtag_withtag('image',self.image_added)
# 棋子与位置标签绑定,方便“杀死”
self.canvas_bottom.addtag_withtag('position'+str(x)+str(y),self.image_added)
deadlist=self.get_deadlist(x,y)
self.kill(deadlist)
# 判断是否重复棋局
if not self.last_2_positions==self.positions:
# 判断是否属于有气和杀死对方其中之一
if len(deadlist)>0 or self.if_dead([[x,y]],self.present+1,[x,y])==False:
# 当不重复棋局,且属于有气和杀死对方其中之一时,落下棋子有效
if not self.regretchance==1:
self.regretchance+=1
else:
self.regretButton['state']=NORMAL
self.last_3_positions=copy.deepcopy(self.last_2_positions)
self.last_2_positions=copy.deepcopy(self.last_1_positions)
self.last_1_positions=copy.deepcopy(self.positions)
# 删除上次的标记,重新创建标记
self.canvas_bottom.delete('image_added_sign')
self.image_added_sign=self.canvas_bottom.create_oval(event.x-dx+round(dx/self.dd)*self.dd+0.5*self.dd, event.y-dy+round(dy/self.dd)*self.dd+0.5*self.dd,event.x-dx+round(dx/self.dd)*self.dd-0.5*self.dd, event.y-dy+round(dy/self.dd)*self.dd-0.5*self.dd,width=3,outline='#3ae')
self.canvas_bottom.addtag_withtag('image',self.image_added_sign)
self.canvas_bottom.addtag_withtag('image_added_sign',self.image_added_sign)
if self.present==0:
self.present=1
self.create_pW()
self.del_pB()
else:
self.present=0
self.create_pB()
self.del_pW()
else:
# 不属于杀死对方或有气,则判断为无气,警告并弹出警告框
self.positions[y][x]=0
self.canvas_bottom.delete('position'+str(x)+str(y))
self.bell()
self.showwarningbox('无气',"此处不可落子!")
else:
# 重复棋局,警告打劫
self.positions[y][x]=0
self.canvas_bottom.delete('position'+str(x)+str(y))
self.recover(deadlist,(1 if self.present==0 else 0))
self.bell()
self.showwarningbox("打劫","此处不可落子!")
else:
# 覆盖,声音警告
self.bell()
else:
# 超出边界,声音警告
self.bell()
# 判断棋子种类为yourChessman位置为yourPosition是否无气死亡有气则返回False无气则返回无气棋子的列表
# 本函数是游戏规则的关键初始deadlist只包含了自己的位置每次执行时函数尝试寻找yourPosition周围有没有空的位置有则结束返回False代表有气
# 若找不到则找自己四周的同类不在deadlist中的是否有气即调用本函数无气则把该同类加入到deadlist然后找下一个邻居只要有一个有气返回False代表有气
# 若四周没有一个有气的同类返回deadlist,至此结束递归
# def if_dead(self,deadlist,yourChessman,yourPosition):
def if_dead(self,deadList,yourChessman,yourPosition):
for i in [-1,1]:
if [yourPosition[0]+i,yourPosition[1]] not in deadList:
if self.positions[yourPosition[1]][yourPosition[0]+i]==0:
return False
if [yourPosition[0],yourPosition[1]+i] not in deadList:
if self.positions[yourPosition[1]+i][yourPosition[0]]==0:
return False
if ([yourPosition[0]+1,yourPosition[1]] not in deadList) and (self.positions[yourPosition[1]][yourPosition[0]+1]==yourChessman):
midvar=self.if_dead(deadList+[[yourPosition[0]+1,yourPosition[1]]],yourChessman,[yourPosition[0]+1,yourPosition[1]])
if not midvar:
return False
else:
deadList+=copy.deepcopy(midvar)
if ([yourPosition[0]-1,yourPosition[1]] not in deadList) and (self.positions[yourPosition[1]][yourPosition[0]-1]==yourChessman):
midvar=self.if_dead(deadList+[[yourPosition[0]-1,yourPosition[1]]],yourChessman,[yourPosition[0]-1,yourPosition[1]])
if not midvar:
return False
else:
deadList+=copy.deepcopy(midvar)
if ([yourPosition[0],yourPosition[1]+1] not in deadList) and (self.positions[yourPosition[1]+1][yourPosition[0]]==yourChessman):
midvar=self.if_dead(deadList+[[yourPosition[0],yourPosition[1]+1]],yourChessman,[yourPosition[0],yourPosition[1]+1])
if not midvar:
return False
else:
deadList+=copy.deepcopy(midvar)
if ([yourPosition[0],yourPosition[1]-1] not in deadList) and (self.positions[yourPosition[1]-1][yourPosition[0]]==yourChessman):
midvar=self.if_dead(deadList+[[yourPosition[0],yourPosition[1]-1]],yourChessman,[yourPosition[0],yourPosition[1]-1])
if not midvar:
return False
else:
deadList+=copy.deepcopy(midvar)
return deadList
# 警告消息框,接受标题和警告信息
def showwarningbox(self,title,message):
self.canvas_bottom.delete(self.cross)
tkinter.messagebox.showwarning(title,message)
# 落子后,依次判断四周是否有棋子被杀死,并返回死棋位置列表
def get_deadlist(self,x,y):
deadlist=[]
for i in [-1,1]:
if self.positions[y][x+i]==(2 if self.present==0 else 1) and ([x+i,y] not in deadlist):
killList=self.if_dead([[x+i,y]],(2 if self.present==0 else 1),[x+i,y])
if not killList==False:
deadlist+=copy.deepcopy(killList)
if self.positions[y+i][x]==(2 if self.present==0 else 1) and ([x,y+i] not in deadlist):
killList=self.if_dead([[x,y+i]],(2 if self.present==0 else 1),[x,y+i])
if not killList==False:
deadlist+=copy.deepcopy(killList)
return deadlist
# 恢复位置列表list_to_recover为b_or_w指定的棋子
def recover(self,list_to_recover,b_or_w):
if len(list_to_recover)>0:
for i in range(len(list_to_recover)):
self.positions[list_to_recover[i][1]][list_to_recover[i][0]]=b_or_w+1
self.image_added=self.canvas_bottom.create_image(20*self.size+(list_to_recover[i][0]-1)*self.dd+4*self.p, 20*self.size+(list_to_recover[i][1]-1)*self.dd-5*self.p,image=self.photoWBD_list[b_or_w])
self.canvas_bottom.addtag_withtag('image',self.image_added)
self.canvas_bottom.addtag_withtag('position'+str(list_to_recover[i][0])+str(list_to_recover[i][1]),self.image_added)
# 杀死位置列表killList中的棋子即删除图片位置值置0
def kill(self,killList):
if len(killList)>0:
for i in range(len(killList)):
self.positions[killList[i][1]][killList[i][0]]=0
self.canvas_bottom.delete('position'+str(killList[i][0])+str(killList[i][1]))
# 键盘快捷键退出游戏
def keyboardQuit(self,event):
self.quit()
# 以下两个函数修改全局变量值newApp使主函数循环以建立不同参数的对象
def newGame1(self):
global mode_num,newApp
mode_num=(13 if self.mode_num==9 else 9)
newApp=True
self.quit()
def newGame2(self):
global mode_num,newApp
mode_num=(13 if self.mode_num==19 else 19)
newApp=True
self.quit()
def newGame3(self):
global mode_num,newApp
mode_num=(9)
newApp=True
self.quit()
# 声明全局变量用于新建Application对象时切换成不同模式的游戏
global mode_num,newApp
mode_num=9
newApp=False
if __name__=='__main__':
# 循环,直到不切换游戏模式
while True:
newApp=False
app=Application(mode_num)
app.title('围棋')
app.mainloop()
if newApp:
app.destroy()
else:
break