软件过程管理实验4-代码

# -*- coding: utf-8 -*-
# 使用Python内置GUI模块tkinter
from tkinter import *
# ttk覆盖tkinter部分对象,ttk对tkinter进行了优化
from tkinter.ttk import *
# 深拷贝时需要用到copy模块
import copy
import tkinter.messagebox

# 默认9路
DEFAULT_MODE_NUM = 9
NEW_APP = False


# 围棋应用对象定义
class Application(Tk):
    # 初始化棋盘,默认九路棋盘
    def __init__(self, mode_num=DEFAULT_MODE_NUM):
        Tk.__init__(self)
        # 模式,九路棋:9,十三路棋:13,十九路棋:19
        self.mode_num = mode_num
        # 窗口尺寸设置,默认:1.8
        self.size = 1.8
        # 棋盘每格的边长
        self.grid_size = 360 * self.size / (self.mode_num - 1)
        # 相对九路棋盘的矫正比例
        self.correction_ratio = 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/try:
            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")
        except Exception as e:
            print(f"Failed to load image: {e}")
        # 用于黑白棋子图片切换的列表
        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.grid_size * (2 if self.mode_num == 9 else (3 if self.mode_num == 13 else 6)),
                    n * self.grid_size * (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.grid_size, 380 * self.size,
                                           20 * self.size + i * self.grid_size, width=2)
            self.canvas_bottom.create_line(20 * self.size + i * self.grid_size, 20 * self.size, 20 * self.size + i * self.grid_size,
                                           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)
        # 鼠标左键单击时,调用place_piece函数,放下棋子
        self.canvas_bottom.bind('<Button-1>', self.place_piece)
        # 设置退出快捷键<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.grid_size
                dy = (event.y - 20 * self.size) % self.grid_size
                self.cross = self.canvas_bottom.create_image(
                    event.x - dx + round(dx / self.grid_size) * self.grid_size + 22 * self.correction_ratio,
                    event.y - dy + round(dy / self.grid_size) * self.grid_size - 27 * self.correction_ratio,
                    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 place_piece(self, event):
        if not self.stop:
            # 先找到最近格点
            if (20 * self.size - self.grid_size * 0.4 < event.x < self.grid_size * 0.4 + 380 * self.size) and \
                    (20 * self.size - self.grid_size * 0.4 < event.y < self.grid_size * 0.4 + 380 * self.size):
                dx = (event.x - 20 * self.size) % self.grid_size
                dy = (event.y - 20 * self.size) % self.grid_size
                x = int((event.x - 20 * self.size - dx) / self.grid_size + round(dx / self.grid_size) + 1)
                y = int((event.y - 20 * self.size - dy) / self.grid_size + round(dy / self.grid_size) + 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.grid_size) * self.grid_size + 4 * self.correction_ratio,
                        event.y - dy + round(dy / self.grid_size) * self.grid_size - 5 * self.correction_ratio,
                        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.grid_size) * self.grid_size + 0.5 * self.grid_size,
                                event.y - dy + round(dy / self.grid_size) * self.grid_size + 0.5 * self.grid_size,
                                event.x - dx + round(dx / self.grid_size) * self.grid_size - 0.5 * self.grid_size,
                                event.y - dy + round(dy / self.grid_size) * self.grid_size - 0.5 * self.grid_size, 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,表示当前棋子无气。
        """
        directions = [[-1, 0], [1, 0], [0, -1], [0, 1]]
        for dx, dy in directions:
            new_x = your_position[0] + dx
            new_y = your_position[1] + dy
            if [new_x, new_y] not in dead_list:
                if self.positions[new_y][new_x] == 0:
                    return False  # 如果有空位,当前棋子有气

        for dx, dy in directions:
            new_x = your_position[0] + dx
            new_y = your_position[1] + dy
            if [new_x, new_y] not in dead_list and self.positions[new_y][new_x] == your_chess:
                mid = self.if_dead(dead_list + [[new_x, new_y]], your_chess, [new_x, new_y])
                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 = []
        directions = [[-1, 0], [1, 0], [0, -1], [0, 1]]
        for dx, dy in directions:
            new_x = x + dx
            new_y = y + dy
            if self.positions[new_y][new_x] == (2 if self.present == 0 else 1) and ([new_x, new_y] not in dead_list):
                kill = self.if_dead([[new_x, new_y]], (2 if self.present == 0 else 1), [new_x, new_y])
                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.grid_size + 4 * self.correction_ratio,
                    20 * self.size + (list_to_recover[i][1] - 1) * self.grid_size - 5 * self.correction_ratio,
                    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 DEFAULT_MODE_NUM, NEW_APP
        DEFAULT_MODE_NUM = (13 if self.mode_num == 9 else 9)
        NEW_APP = True
        self.quit()

    def new_game_second(self):
        global DEFAULT_MODE_NUM, NEW_APP
        DEFAULT_MODE_NUM = (13 if self.mode_num == 19 else 19)
        NEW_APP = True
        self.quit()


if __name__ == '__main__':
    # 循环,直到不切换游戏模式
    while True:
        NEW_APP = False
        app = Application(DEFAULT_MODE_NUM)
        app.title('围棋')
        app.mainloop()
        if NEW_APP:
            app.destroy()
        else:
            break

 

posted @ 2025-06-12 17:22  chrisrmas、  阅读(11)  评论(0)    收藏  举报