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.

452 lines
17 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 QPainter, QPen, QFont, QPixmap
from PySide6.QtWidgets import QApplication, QMainWindow, QGraphicsLineItem, QMessageBox, QGraphicsScene
from main_ui import Ui_MainWindow
import data
from graph_24 import *
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.show_card)
self.pushButton_ex.clicked.connect(self.show_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)
def secen_clear(self):
self.scene.clear()
self.selected_id = []
self.value_nodes = []
self.TreeNode = []
self.adds_id = set()
self.trees_root = []
self.drawn_tree_id = set()
self.selected_value = None # 当前选择的卡片值.
self.selected_ops = None # 选择的运算符
# 清空运算符显示
self.lb_ops_selected.setText("")
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)
else:
QMessageBox.warning(self, '警告', '请先选择两个节点')
else:
QMessageBox.warning(self, '警告', '请先选择一个运算符')
def exchange_value_node(self):
# 修改节点值
if len(self.selected_id) == 1:
if self.selected_value:
if self.TreeNode:
for node in self.TreeNode:
if node.NodeID == self.selected_id[0]:
node.FaceValue = 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:
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 = 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):
self.pushButton_ast.setEnabled(True)
self.pushButton_allan.setEnabled(True)
self.pushButton_dtree.setEnabled(True)
self.pushButton_autosv.setEnabled(True)
self.p1_selected.setStyleSheet("border-image: none;")
# 原始数组
self.lineEdit.clear()
self.label_result.setText(" =? ")
self.label_TF.setText("")
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 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"={result}")
except:
QMessageBox.warning(self, '警告', '表达式错误!')
return
if self.TreeNode:
root = self.TreeNode[-1]
result = data.calculate(root, self.TreeNode)
self.label_result.setText(f"={result}")
self.result_ToF(int(result))
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 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):
# 绘制根节点
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)
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)
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)")
if __name__ == '__main__':
app = QApplication()
window = MainWindow()
window.show()
app.exec()