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.

530 lines
19 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.

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()