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

8 months ago
import random
from PySide6.QtGui import QFont
8 months ago
from PySide6.QtWidgets import QApplication, QMainWindow, QGraphicsLineItem, QMessageBox, QGraphicsScene
import data
from graph_24 import *
from main_ui import Ui_MainWindow
8 months ago
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, '警告', '当前无语法树')
8 months ago
if __name__ == '__main__':
app = QApplication()
window = MainWindow()
window.show()
app.exec()