效果图
完整代码
源码地址: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