效果图
完整代码
源码地址:Python 围棋
# 使用Python内置GUI模块tkinter
from tkinter import *
# ttk覆盖tkinter部分对象,ttk对tkinter进行了优化
from tkinter.ttk import *
# 深拷贝时需要用到copy模块
import copy
import tkinter.messagebox # 默认9路
MODE_NUM = 9
NEW_APP = False # 围棋应用对象定义
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.8 # 棋盘每格的边长 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 # 当前轮到的玩家,黑:0,白:1,执黑先行 self.present = 0 # 设置先手 self.fm = -1 # 棋子阴影 self.cross = None # 记录空位置 self.image_added = None self.image_added_sign = None # 初始停止运行,点击“开始游戏”运行游戏 self.stop = True # 悔棋次数,次数大于0才可悔棋,初始置0(初始不能悔棋),悔棋后置0,下棋或弃手时恢复为1,以禁止连续悔棋 self.regret_chance = 0 # 图片资源,存放在当前目录下的/images/中 self.image_W = PhotoImage(file="./images/WD-9.png") self.image_B = PhotoImage(file="./images/BD-9.png") self.image_BD = PhotoImage(file="./images/" + "BD" + "-" + str(self.mode_num) + ".png") self.image_WD = PhotoImage(file="./images/" + "WD" + "-" + str(self.mode_num) + ".png") self.image_BU = PhotoImage(file="./images/" + "BU" + "-" + str(self.mode_num) + ".png") self.image_WU = PhotoImage(file="./images/" + "WU" + "-" + str(self.mode_num) + ".png") # 用于黑白棋子图片切换的列表 self.chequer_wbu_list = [self.image_BU, self.image_WU] self.chequer_wbd_list = [self.image_BD, self.image_WD] # 窗口大小 self.geometry(str(int(600 * self.size)) + 'x' + str(int(400 * self.size))) # 画布控件,作为容器 self.canvas_bottom = Canvas(self, bg='#585858', 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.giveUpButton = Button(self, text='弃一手', command=self.give_up) self.giveUpButton.place(x=480 * self.size, y=225 * self.size) self.regretButton = Button(self, text='悔棋', command=self.regret_chess) 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.new_game_one) self.newGameButton1.place(x=480 * self.size, y=300 * self.size) self.newGameButton2 = Button(self, text=('十三' if self.mode_num == 19 else '十九') + '路棋', command=self.new_game_second) self.newGameButton2.place(x=480 * self.size, y=325 * self.size) self.quitButton = Button(self, text='退出游戏', command=self.quit) self.quitButton.place(x=480 * self.size, y=350 * self.size) # 画棋盘,填充颜色 self.canvas_bottom.create_rectangle(0 * self.size, 0 * self.size, 400 * self.size, 400 * self.size, fill='#d0892e') # 刻画棋盘线及九个点 # 先画外框粗线 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.original = 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.original, 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) # 放置右侧初始图片 self.pW = None # 默认黑棋先手 self.pB = self.canvas_bottom.create_image(500 * self.size + 11, 65 * self.size, image=self.image_B) # 每张图片都添加image标签,方便reload函数删除图片 self.canvas_bottom.addtag_withtag('image', self.pB) self.bButton = Button(self, text='黑棋先手', command=self.first_b) self.bButton.place(x=480 * self.size, y=100 * self.size) self.wButton = Button(self, text='白棋先手', command=self.first_w) self.wButton.place(x=480 * self.size, y=120 * self.size) # 鼠标移动时,调用shadow函数,显示随鼠标移动的棋子 self.canvas_bottom.bind('<Motion>', self.shadow) # 鼠标左键单击时,调用get_down函数,放下棋子 self.canvas_bottom.bind('<Button-1>', self.get_down) # 设置退出快捷键<Ctrl>+<D>,快速退出游戏 self.bind('<Control-KeyPress-d>', self.keyboard_quit) def first_b(self): """ @summary: 黑棋先手 :return: """ self.present = 0 self.create_pb() self.del_pw() if self.stop: self.bButton['state'] = DISABLED self.wButton['state'] = NORMAL else: self.bButton['state'] = DISABLED self.wButton['state'] = DISABLED def first_w(self): """ @summary: 白棋先手 :return: """ self.present = 1 self.create_pw() self.del_pb() if self.stop: self.wButton['state'] = DISABLED self.bButton['state'] = NORMAL else: self.bButton['state'] = DISABLED self.wButton['state'] = DISABLED # 开始游戏函数,点击“开始游戏”时调用 def start(self): # 禁止选先手 self.bButton['state'] = DISABLED self.wButton['state'] = DISABLED # 利用右侧图案提示开始时谁先落子 if self.present == 0: self.create_pb() self.del_pw() else: self.create_pw() self.del_pb() # 开始标志,解除stop self.stop = None # 放弃一手函数,跳过落子环节 def give_up(self): # 悔棋恢复 if not self.regret_chance == 1: self.regret_chance += 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_chess(self): # 判定是否可以悔棋,以前第三盘棋局复原棋盘 if self.regret_chance == 1: self.regret_chance = 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.regret_chance = 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): """ @summary: 创建白棋 :return: """ self.pW = self.canvas_bottom.create_image(500 * self.size + 11, 65 * self.size, image=self.image_W) self.canvas_bottom.addtag_withtag('image', self.pW) def create_pb(self): """ @summary: 创建黑棋 :return: """ self.pB = self.canvas_bottom.create_image(500 * self.size + 11, 65 * self.size, image=self.image_B) self.canvas_bottom.addtag_withtag('image', self.pB) def del_pw(self): if self.pW: self.canvas_bottom.delete(self.pW) def del_pb(self): if self.pB: self.canvas_bottom.delete(self.pB) # 显示鼠标移动下棋子的移动 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.chequer_wbu_list[self.present]) self.canvas_bottom.addtag_withtag('image', self.cross) if self.cross_last is not None: self.canvas_bottom.delete(self.cross_last) self.cross_last = self.cross # 落子,并驱动玩家的轮流下棋行为 def get_down(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.chequer_wbd_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) dead_list = self.get_dead_list(x, y) self.kill(dead_list) # 判断是否重复棋局 if not self.last_2_positions == self.positions: # 判断是否属于有气和杀死对方其中之一 if len(dead_list) > 0 or self.if_dead([[x, y]], self.present + 1, [x, y]) == False: # 当不重复棋局,且属于有气和杀死对方其中之一时,落下棋子有效 if not self.regret_chance == 1: self.regret_chance += 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.create_pw() self.del_pb() self.present = 1 else: self.create_pb() self.del_pw() self.present = 0 else: # 不属于杀死对方或有气,则判断为无气,警告并弹出警告框 self.positions[y][x] = 0 self.canvas_bottom.delete('position' + str(x) + str(y)) self.bell() self.show_warning_box('无气', "你被包围了!") else: # 重复棋局,警告打劫 self.positions[y][x] = 0 self.canvas_bottom.delete('position' + str(x) + str(y)) self.recover(dead_list, (1 if self.present == 0 else 0)) self.bell() self.show_warning_box("打劫", "此路不通!") else: # 覆盖,声音警告 self.bell() else: # 超出边界,声音警告 self.bell() def if_dead(self, dead_list, your_chess, your_position): """ 判断棋子(种类为 your_chess,位置为 your_position)是否无气(死亡)。 如果棋子有气,则返回 False,表示棋子存活。 如果棋子无气,则返回包含所有无气棋子位置的列表。 参数: - dead_list: 一个列表,初始时包含当前正在检查的棋子的位置。 - your_chess: 当前正在检查的棋子的种类。 - your_position: 当前正在检查的棋子的位置。 返回值: - 如果棋子有气,返回 False。 - 如果棋子无气,返回包含所有无气棋子位置的列表。 函数逻辑: 1. 检查当前棋子周围是否有空位,如果有,则棋子有气,返回 False。 2. 如果周围没有空位,检查周围是否有同类棋子,如果有,则递归调用 if_dead 函数检查这些棋子是否有气。 3. 如果递归调用返回 False,表示至少有一个同类棋子有气,当前棋子也有气,返回 False。 4. 如果递归调用返回一个列表,表示所有检查的同类棋子都无气,将这些棋子的位置添加到 dead_list 中。 5. 如果所有周围的同类棋子都检查完毕且都无气,返回 dead_list,表示当前棋子无气。 """ # 检查上下左右四个方向是否有空位 for i in [-1, 1]: # 检查上方和下方 if [your_position[0] + i, your_position[1]] not in dead_list: if self.positions[your_position[1]][your_position[0] + i] == 0: return False # 如果有空位,当前棋子有气 # 检查左侧和右侧 if [your_position[0], your_position[1] + i] not in dead_list: if self.positions[your_position[1] + i][your_position[0]] == 0: return False # 如果有空位,当前棋子有气 # 检查四个方向上是否有同类棋子,并递归检查这些棋子是否有气 # 上方的同类棋子 if ([your_position[0] + 1, your_position[1]] not in dead_list) and ( self.positions[your_position[1]][your_position[0] + 1] == your_chess): mid = self.if_dead(dead_list + [[your_position[0] + 1, your_position[1]]], your_chess, [your_position[0] + 1, your_position[1]]) if not mid: return False # 如果上方同类棋子有气,则当前棋子也有气 else: dead_list += copy.deepcopy(mid) # 如果无气,将棋子位置添加到列表中 # 下方的同类棋子,逻辑同上 # ... # 左侧的同类棋子,逻辑同上 # ... # 右侧的同类棋子,逻辑同上 # ... # 如果所有检查都完成,没有找到有气的同类棋子,则当前棋子无气,返回包含所有无气棋子位置的列表 return dead_list # 警告消息框,接受标题和警告信息 def show_warning_box(self, title, message): self.canvas_bottom.delete(self.cross) tkinter.messagebox.showwarning(title, message) # 落子后,依次判断四周是否有棋子被杀死,并返回死棋位置列表 def get_dead_list(self, x, y): dead_list = [] for i in [-1, 1]: if self.positions[y][x + i] == (2 if self.present == 0 else 1) and ([x + i, y] not in dead_list): kill = self.if_dead([[x + i, y]], (2 if self.present == 0 else 1), [x + i, y]) if kill: dead_list += copy.deepcopy(kill) if self.positions[y + i][x] == (2 if self.present == 0 else 1) and ([x, y + i] not in dead_list): kill = self.if_dead([[x, y + i]], (2 if self.present == 0 else 1), [x, y + i]) if kill: dead_list += copy.deepcopy(kill) return dead_list # 恢复位置列表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.chequer_wbd_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, kill_list): if len(kill_list) > 0: for i in range(len(kill_list)): self.positions[kill_list[i][1]][kill_list[i][0]] = 0 self.canvas_bottom.delete('position' + str(kill_list[i][0]) + str(kill_list[i][1])) # 键盘快捷键退出游戏 def keyboard_quit(self, event): self.quit() # 以下两个函数修改全局变量值,newApp使主函数循环,以建立不同参数的对象 def new_game_one(self): global MODE_NUM, NEW_APP MODE_NUM = (13 if self.mode_num == 9 else 9) NEW_APP = True self.quit() def new_game_second(self): global MODE_NUM, NEW_APP MODE_NUM = (13 if self.mode_num == 19 else 19) NEW_APP = True self.quit() # 声明全局变量,用于新建Application对象时切换成不同模式的游戏 if __name__ == '__main__': # 循环,直到不切换游戏模式 while True: NEW_APP = False app = Application(MODE_NUM) app.title('围棋') app.mainloop() if NEW_APP: app.destroy() else: break
源码地址:Python 围棋
实现思路
这段 Python 代码实现了一个基于 tkinter 的围棋游戏 GUI。以下是实现的主要思路:
-
初始化界面:创建一个 Tk 窗口,设置窗口大小,并加载所需的图片资源。
-
棋盘和棋子:初始化一个二维数组来表示棋盘,数组中的元素表示棋盘上的位置状态(空、黑棋、白棋)。使用
Canvas
控件绘制棋盘和棋子。 -
棋子下法:通过鼠标点击画布来放置棋子。棋子的放置逻辑包括判断位置是否有效、是否被占据,以及放置后是否形成打劫(即重复之前的局面)。
-
悔棋功能:允许玩家悔棋,即撤销上一步操作。悔棋后棋盘状态回退到前一次的状态。
-
重新开始游戏:允许玩家重新开始游戏,重置棋盘状态和界面元素。
-
切换棋盘大小:支持 9 路、13 路和 19 路棋盘,玩家可以通过按钮切换棋盘大小。
-
玩家交替落子:通过变量
present
来记录当前轮到哪个玩家落子,0 表示黑棋,1 表示白棋。 -
判断死活:实现了一个递归函数
if_dead
来判断棋子是否被完全包围(即无气),如果是,则认为该棋子死亡。 -
界面交互:包括开始游戏、弃一手(跳过当前回合)、悔棋、重新开始、切换棋盘大小和退出游戏等按钮。
-
图形用户界面元素:使用 tkinter 的
Button
、Canvas
等组件来构建用户界面。 -
事件绑定:通过绑定鼠标事件和键盘事件来响应用户的交互操作。
-
辅助功能:包括显示警告框、声音提示等,以增强用户体验。
整体上,这段代码通过 tkinter 模块实现了一个基本的围棋游戏,包括棋盘的绘制、棋子的下法、悔棋功能以及基本的用户交互。代码结构清晰,功能模块化,易于理解和扩展。
源码地址:Python 围棋
Python 爱心代码:https://stormsha.blog.csdn.net/article/details/138199890
Python 植物大战僵尸:https://stormsha.blog.csdn.net/article/details/138405944
Python 开心消消乐:https://stormsha.blog.csdn.net/article/details/139220748