import random from PySide6.QtGui import QFont from PySide6.QtWidgets import QApplication, QMainWindow, QGraphicsLineItem, QMessageBox, QGraphicsScene import data from graph_24 import * from main_ui import Ui_MainWindow class MainWindow(QMainWindow, Ui_MainWindow): def __init__(self): super().__init__() self.drawn_tree_id = set() # 已经绘制了树的节点 self.seq = 1 # 在有树的情况下,添加节点的序号,以确定新加的节点位置 self.trees_root = [] # 保存所有树的根节点 self.selected_id = [] # 选中的节点ID self.adds_id = set() # 已经用过的ID self.value_nodes = [] # 数值节点 # self.operator_nodes = [] self.selected_ops = None # 选择的运算符 self.selected_value = None # 当前选择的卡片值 self.r_count = 0 # 记录所有答案点击次数 self.offset = 0 # 语法树遍历时随深度平移长度,避免节点重合 self.TreeNode = [] self.Tree_4_nums = None self.setupUi(self) self.init_ui() self.init_slot() self.pushButton_q.clicked.connect(self.question_card) # 出题 self.pushButton_ex.clicked.connect(self.change_card) # 换牌 self.pushButton_autosv.clicked.connect(self.AutoTest_9_10) # 自动求解 self.pushButton_dtree.clicked.connect(self.draw_tree) # 绘制语法树 self.pushButton_ast.clicked.connect(self.calculate_button) # 生成语法树 self.pushButton_allan.clicked.connect(self.AutoTest) # 自动求解 self.clear_button.clicked.connect(self.secen_clear) # 清空画布 self.pushButton_add.clicked.connect(lambda: self.select_ops('+')) self.pushButton_sub.clicked.connect(lambda: self.select_ops('-')) self.pushButton_mul.clicked.connect(lambda: self.select_ops('*')) self.pushButton_div.clicked.connect(lambda: self.select_ops('/')) self.add_val_node.clicked.connect(self.add_value_node) # 添加数值节点 self.add_ops_node.clicked.connect(self.add_operator_node) # 添加运算符节点 self.ex_node_value.clicked.connect(self.exchange_value_node) # 修改数值节点 self.ex_node_ops.clicked.connect(self.exchange_ops_node) # 修改运算符节点 self.ex_node_self.clicked.connect(self.exchange_two_nodes) # 交换两个节点 self.pushButton_exp.clicked.connect(self.agent_exp) # 生成表达式 self.pushButton_check_te.clicked.connect(self.check_tree_and_expression) # 验证语法树和表达式是否匹配 def secen_clear(self): self.scene.clear() self.selected_id = [] # 选中的节点ID self.value_nodes = [] # 数值节点 self.TreeNode = [] # 所有节点 self.adds_id = set() # 已经用过的ID self.trees_root = [] # 保存所有树的根节点 self.drawn_tree_id = set() # 已经绘制了树的节点 self.selected_value = None # 当前选择的卡片值. self.selected_ops = None # 选择的运算符 # 清空运算符显示 self.lb_ops_selected.setText("") # 运算符显示 self.p1_selected.setStyleSheet("border-image: none;") # 清空已选牌面 self.pushButton_ast.setEnabled(True) self.pushButton_allan.setEnabled(True) self.pushButton_dtree.setEnabled(True) self.pushButton_autosv.setEnabled(True) def select_ops(self, ops): self.lb_ops_selected.setText(ops) self.selected_ops = ops def add_value_node(self): # self.scene.clear() if self.selected_value is not None: # 选择了卡片值 self.value_nodes.append(self.selected_value) # 添加到节点数值列表 for i in range(len(self.adds_id) + 1): if i not in self.adds_id: value = self.selected_value nodet = RoundedRectangleItem(150 * self.seq, 0, str(value), i) self.seq += 1 # 序号加1 nodet.selected.connect(lambda id: self.selected_node(id)) # 连接信号 nodet.unselected.connect(lambda id: self.unselected_node(id)) node = data.Node([i, 'Value', None, None, None, value, None]) self.adds_id.add(i) # 添加全局id self.TreeNode.append(node) self.scene.addItem(nodet) else: QMessageBox.warning(self, '警告', '请先选择一张牌') def judge_node_conn(self): # 判断节点是否已经连接 for id in self.selected_id: for node in self.TreeNode: if node.LeftNodeID == id or node.RightNodeID == id: return True def add_operator_node(self): # 添加运算符节点 if self.selected_ops is not None: if len(self.selected_id) == 2: # 判断已选节点是否已经被连接 if self.judge_node_conn(): QMessageBox.warning(self, '警告', '节点已经连接') return for i in range(len(self.adds_id) + 1): # 确保id唯一 if i not in self.adds_id: node = data.Node([i, 'Operator', self.selected_ops, None, None, None, None]) self.TreeNode.append(node) self.adds_id.add(i) # 全局ID node.LeftNodeID = int(self.selected_id[1]) node.RightNodeID = int(self.selected_id[0]) self.flash_node(self.selected_id[0]) self.flash_node(self.selected_id[1]) self.selected_id = [] # 清空选中的节点 self.trees_root.append(node) # 保存根节点 # 从根节点列表中删去作为子节点连接的节点 node_to_remove = [] for root in self.trees_root: if root.NodeID == node.LeftNodeID or root.NodeID == node.RightNodeID: node_to_remove.append(root) for root in node_to_remove: self.trees_root.remove(root) print("已用ID", self.adds_id) self.scene.clear() for i, root in enumerate(self.trees_root): if self.has_both_operators(self.TreeNode): self.offset = 35 self.ShowTree(root, self.TreeNode, i * 300, 0) self.offset = 0 self.seq = 1 # 重置序号 for noed in self.TreeNode: print("运算符节点", noed) # exps = data.get_expression(self.TreeNode) # print("表达式", exps) else: QMessageBox.warning(self, '警告', '请先选择两个节点') else: QMessageBox.warning(self, '警告', '请先选择一个运算符') # 根据节点ID,找到对应的节点 def find_node(self, id): for node in self.TreeNode: if node.NodeID == id: return node def exchange_value_node(self): # 修改节点值 if len(self.selected_id) == 1: node = self.find_node(self.selected_id[0]) if node.NodeType == 'Operator': QMessageBox.warning(self, '警告', '请选择数值节点') return if self.selected_value: if self.TreeNode: for node in self.TreeNode: if node.NodeID == self.selected_id[0]: node.FaceValue = str(self.selected_value) for nodet in self.scene.items(): if isinstance(nodet, RoundedRectangleItem): if nodet.id == self.selected_id[0]: nodet.text = str(self.selected_value) nodet.update() else: QMessageBox.warning(self, '警告', '请先选择一张牌') else: QMessageBox.warning(self, '警告', '请先选择一个节点') def exchange_ops_node(self): # 修改节点运算符 if len(self.selected_id) == 1: node = self.find_node(self.selected_id[0]) if node.NodeType == 'Value': QMessageBox.warning(self, '警告', '请选择运算符节点') return if self.selected_ops: if self.TreeNode: for node in self.TreeNode: if node.NodeID == self.selected_id[0]: node.Ops = self.selected_ops for nodet in self.scene.items(): if isinstance(nodet, RoundedRectangleItem): if nodet.id == self.selected_id[0]: nodet.text = str(self.selected_ops) nodet.update() else: QMessageBox.warning(self, '警告', '请先选择一个运算符') else: QMessageBox.warning(self, '警告', '请先选择一个节点') def exchange_two_nodes(self): # 交换选中两个节点FaceValue的值 if len(self.selected_id) == 2: if self.TreeNode: node_1 = None node_2 = None for node in self.TreeNode: if node.NodeID == self.selected_id[0]: if node.NodeType == 'Value': node_1 = node else: QMessageBox.warning(self, '警告', '请选择两个数值节点') return elif node.NodeID == self.selected_id[1]: if node.NodeType == 'Value': node_2 = node else: QMessageBox.warning(self, '警告', '请选择两个数值节点') return if node_1 and node_2: node_1.FaceValue, node_2.FaceValue = node_2.FaceValue, node_1.FaceValue for item in self.scene.items(): if isinstance(item, RoundedRectangleItem): if item.id == self.selected_id[0]: item.text = str(node_1.FaceValue) item.update() elif item.id == self.selected_id[1]: item.text = str(node_2.FaceValue) item.update() else: QMessageBox.warning(self, '警告', '请选择两个节点') def selected_node(self, id): if len(self.selected_id) < 2: self.selected_id.append(id) else: QMessageBox.warning(self, '警告', '不能选择超过3个节点') for nodet in self.scene.items(): # 遍历场景中的所有图元 if isinstance(nodet, RoundedRectangleItem): # 如果是圆角矩形 if nodet.id == id: # 如果是当前选中的节点 nodet.isSelected = False # 取消选中 nodet.update() # 更新图元 def unselected_node(self, id): self.selected_id.remove(id) print(self.selected_id) def flash_node(self, id): # 还原节点为未选中状态 for nodet in self.scene.items(): # 遍历场景中的所有图元 if isinstance(nodet, RoundedRectangleItem): # 如果是圆角矩形 if nodet.id == id: # 如果是当前选中的节点 nodet.isSelected = False # 取消选中 nodet.update() # 更新图元 def init_ui(self): self.scene = QGraphicsScene() self.graphicsView.setScene(self.scene) def init_slot(self): self.pushButton.clicked.connect(self.on_calc) def load_image(self, path, Tree_4_nums, index): def current_selection(value, path): self.selected_value = str(value) self.p1_selected.setStyleSheet(f"border-image: url({path});") self.pushButton_ast.setEnabled(False) self.pushButton_allan.setEnabled(False) self.pushButton_dtree.setEnabled(False) self.pushButton_autosv.setEnabled(False) # print(value) if index == 0: self.p1.setStyleSheet(f"border-image: url({path});") self.p1.clicked.connect(lambda: current_selection(Tree_4_nums[index], path)) elif index == 1: self.p2.setStyleSheet(f"border-image: url({path});") self.p2.clicked.connect(lambda: current_selection(Tree_4_nums[index], path)) elif index == 2: self.p3.setStyleSheet(f"border-image: url({path});") self.p3.clicked.connect(lambda: current_selection(Tree_4_nums[index], path)) elif index == 3: self.p4.setStyleSheet(f"border-image: url({path});") self.p4.clicked.connect(lambda: current_selection(Tree_4_nums[index], path)) def show_card(self): # 原始数组 original_list = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13] face_card = ['Club', 'Diamond', 'Heart', 'Spade'] # 从原始数组中随机选择4个元素组成新列表 self.Tree_4_nums = random.sample(original_list, 4) # 从花色中随机选择一个 for index, num in enumerate(self.Tree_4_nums): card_face = random.choice(face_card) if num == 11: card_path = f"card/{card_face}/{card_face}j.png" elif num == 12: card_path = f"card/{card_face}/{card_face}q.png" elif num == 13: card_path = f"card/{card_face}/{card_face}k.png" else: card_path = f"card/{card_face}/{card_face}{num}.png" self.load_image(card_path, self.Tree_4_nums, index) # return self.Tree_4_nums def question_card(self): # 显示题目卡片 self.secen_clear() # 清空画布 self.lineEdit.clear() self.label_result.setText(" =? ") self.label_TF.setText("") self.show_card() # 显示卡片 # 换牌 def change_card(self): self.show_card() # 显示卡片 def AutoTest(self): if self.Tree_4_nums is None: QMessageBox.warning(self, '警告', '请先出题') return else: results = data.get_Aexps(self.Tree_4_nums) if results == "无法得出24点": self.lineEdit.clear() self.lineEdit.setText("无法得出24点") return else: if self.r_count < len(results): self.lineEdit.clear() self.lineEdit.setText(f"{results[self.r_count]}") self.on_calc() # 自动计算 self.draw_tree() # 绘制语法树 self.r_count += 1 else: QMessageBox.information(self, '提示', '已经是最后一个表达式了') self.r_count = 0 def AutoTest_9_10(self): if self.Tree_4_nums is None: QMessageBox.warning(self, '警告', '请先出题') return else: aexp = data.gen_exp(self.Tree_4_nums) if aexp == "无法得出24点": self.lineEdit.clear() self.lineEdit.setText("无法得出24点") else: self.lineEdit.clear() self.lineEdit.setText(f"{aexp}") self.on_calc() # 自动计算 self.draw_tree() # 绘制语法树 def calculate_button(self): # 计算生成语法树 self.scene.clear() aexp = self.lineEdit.text() try: exp_ls = data.get_postfix_expression(aexp) # 获取后缀表达式列表 self.TreeNode = data.ExpressionAnalyse(exp_ls) # 将后缀表达式转化为节点树 result = data.calculate(self.TreeNode[-1], self.TreeNode) self.label_result.setText(f"={round(result, 2)}") except: QMessageBox.warning(self, '警告', '表达式错误!') return if self.TreeNode: # 如果节点树不为空 root = self.TreeNode[-1] result = data.calculate(root, self.TreeNode) # 计算表达式的值 self.label_result.setText(f"={round(result, 2)}") self.result_ToF(int(result)) # 判断结果是否为24 if self.has_both_operators(self.TreeNode): self.offset = 35 self.ShowTree(root, self.TreeNode) self.offset = 0 def has_both_operators(self, TreeNode): # 判断是否同时有两个运算符 ops = data.OPS def find_op_by_id(id): # 根据ID查找运算符 for node in TreeNode: if node.NodeID == id and node.NodeType == "Operator": return node.Ops for node in TreeNode: # 遍历节点树 if node.NodeType == "Operator": opl = find_op_by_id(node.LeftNodeID) opr = find_op_by_id(node.RightNodeID) if opl in ops and opr in ops: return True def check_tree_and_expression(self): # 检查节点树和表达式是否一致 if self.TreeNode is not None: aexp = self.lineEdit.text() if aexp == '': QMessageBox.warning(self, '警告', '请输入表达式') return try: exp_ls = data.get_postfix_expression(aexp) # 获取后缀表达式列表 TreeNode2 = data.ExpressionAnalyse(exp_ls) # 将后缀表达式转化为节点树 if self.check_tree(self.TreeNode, TreeNode2): QMessageBox.information(self, '提示', '节点树与表达式一致') else: QMessageBox.warning(self, '警告', '节点树与表达式不一致') except Exception as e: QMessageBox.warning(self, '警告', '表达式错误') return else: QMessageBox.warning(self, '警告', '当前无语法树') # 检查两个节点树是否一致的函数 def check_tree(self, TreeNode1, TreeNode2): if len(TreeNode1) != len(TreeNode2): return False for i in range(len(TreeNode1)): if TreeNode1[i].NodeID != TreeNode2[i].NodeID: return False if TreeNode1[i].NodeType != TreeNode2[i].NodeType: return False if TreeNode1[i].Ops != TreeNode2[i].Ops: return False if TreeNode1[i].FaceValue != TreeNode2[i].FaceValue: return False if TreeNode1[i].LeftNodeID != TreeNode2[i].LeftNodeID: return False if TreeNode1[i].RightNodeID != TreeNode2[i].RightNodeID: return False return True def draw_tree(self): self.scene.clear() aexp = self.lineEdit.text() if aexp == "": QMessageBox.warning(self, '警告', '请输入表达式') return try: exp_ls = data.get_postfix_expression(aexp) # 获取后缀表达式列表 self.TreeNode = data.ExpressionAnalyse(exp_ls) # 将后缀表达式转化为节点树 root = self.TreeNode[-1] if self.has_both_operators(self.TreeNode): self.offset = 35 self.ShowTree(root, self.TreeNode, depth=0) self.offset = 0 except Exception as e: QMessageBox.warning(self, '警告', '表达式错误') def ShowTree(self, root, TreeNode, startx=0, starty=0, depth=0): self.selected_id = [] # 清空选中的节点 self.drawn_tree_id = set() # 清空已绘制的节点ID # 绘制根节点 if root.NodeType == 'Value': nds_root = RoundedRectangleItem(startx, starty, root.FaceValue, root.NodeID) nds_root.selected.connect(lambda id: self.selected_node(id)) nds_root.unselected.connect(lambda id: self.unselected_node(id)) self.drawn_tree_id.add(root.NodeID) self.adds_id.add(root.NodeID) else: nds_root = RoundedRectangleItem(startx, starty, root.Ops, root.NodeID) nds_root.selected.connect(lambda id: self.selected_node(id)) nds_root.unselected.connect(lambda id: self.unselected_node(id)) self.drawn_tree_id.add(root.NodeID) self.adds_id.add(root.NodeID) nds_root.setZValue(1) self.scene.addItem(nds_root) # 计算节点间的水平偏移量 horizontal_offset = 100 - depth * self.offset # 计算左子树和右子树的位置 left_x, left_y = startx - horizontal_offset, starty + 100 right_x, right_y = startx + horizontal_offset, starty + 100 # 绘制左子树 if root.LeftNodeID is not None: left_child = next(node for node in TreeNode if node.NodeID == root.LeftNodeID) self.ShowTree(left_child, TreeNode, left_x, left_y, depth + 1) # 绘制左子树与根节点之间的连线 line_left = QGraphicsLineItem(startx + 35, starty + 35, left_x + 35, left_y + 35) line_left.setZValue(-1) line_left.setPen(QPen(Qt.red, 3)) self.scene.addItem(line_left) # 绘制右子树 if root.RightNodeID is not None: right_child = next(node for node in TreeNode if node.NodeID == root.RightNodeID) self.ShowTree(right_child, TreeNode, right_x, right_y, depth + 1) # 绘制右子树与根节点之间的连线 line_right = QGraphicsLineItem(startx + 35, starty + 35, right_x + 35, right_y + 35) line_right.setZValue(-1) line_right.setPen(QPen(Qt.red, 3)) self.scene.addItem(line_right) def on_calc(self): aexp = self.lineEdit.text() if aexp == '': QMessageBox.warning(self, '警告', '请输入表达式') return try: result = eval(aexp) self.label_result.setText(f"={int(result)}") # 设置字体样式 self.result_ToF(result) except Exception as e: QMessageBox.warning(self, '警告', '表达式错误') return def result_ToF(self, result): font = QFont('Microsoft YaHei', 16) font.setBold(True) # 将字体设置为粗体 self.label_TF.setText('√' if result == 24 else '×') # 将字体样式应用到 QLabel 上 self.label_TF.setFont(font) # 将文本颜色设置为红色 self.label_TF.setStyleSheet("color: red;background-color:rgb(219, 205, 166)") def agent_exp(self): if len(self.TreeNode) > 0: exp_str = data.get_expression(self.TreeNode) if exp_str is not None: self.label_result.clear() # 清空显示结果 self.lineEdit.clear() # 清空显示表达式 self.lineEdit.setText(exp_str) # 显示表达式 else: QMessageBox.warning(self, '警告', '无法生成表达式') else: QMessageBox.warning(self, '警告', '当前无语法树') if __name__ == '__main__': app = QApplication() window = MainWindow() window.show() app.exec()