同样也是学校的简单AI实验,主要的难点在于GUI的设置和动画的演绎过程!这个时间的配置非常关键,多一点和少一点就完全破坏了平衡,所以调试了好久,终于找到了一个比较好的展现过程,当然也有用线程的各种花式玩法,来实现这种动画的效果,也可以尝试把所有动画放在一起之类的操作,但是我这里由于用的时候是N个动画并行执行,所以就不乱来了,(主要是太菜了)。
核心算法部分:
# -*- coding: utf-8 -*-
# @Time : 2022-04-26 13:30
# @Site : 牧师-野人问题的算法核心
# @File : MCK.py
# @Software: PyCharm
import operator
import time
import copy
class State:
def __init__(self, m, c, b):
"""
:param m:左岸传教士数量
:param c:左岸野人数量
:param b:1表示现在船停在左岸,0表示船停在右岸
"""
self.m = m # 左岸传教士数量
self.c = c # 左岸野人数量
self.b = b # b = 1: 船在左岸;b = 0: 船在右岸
self.steps = 0 # g表示经过了多少次的循环
self.score = 0 # score = steps+h
self.father = None # 用于递归进行定义后面的常态
self.data = [m, c, b] # 简化判断的过程
def __lt__(self, other):
return self.score < other.score # 自定义的函数,用于后面的判断
class solution:
def __init__(self, M, C, K):
"""
:param M: 初始的时候左岸的传教士数量
:param C: 初始的时候左岸的野人的数量
:param K: 船最大可容纳人数
"""
self.M = M
self.C = C
self.K = K
self.init = State(M, C, 1) # 赋值初始状态图,传教士和野人全部在左岸,船也在左岸
self.goal = State(0, 0, 0) # 赋值目标状态图,传教士和野人全部在右岸,船也在右岸
self.openList = [] # open表
self.closeList = set() # 根据hash表来定义的close表
def getHash(self, nums):
"""
:param nums:为状态图,通过状态的方案可以简单得到,这种三个数字的拼接可以组成唯一的标识状态
:return:定义hash值函数,通过状态的三值:m,c,b,来定位状态
"""
tags = str(nums[0]) + "a" + str(nums[1]) + "a" + str(nums[2]) # 会有特殊情况比如(11,0,1)与(1,10,1)的hash数值会一样
return abs(hash(tags)) # 在中间加上间隔"a"来划分间隔,标识化hash数值
def safe(self, s):
"""
:param s:标识一个状态state,m,c,k
:return:返回bool数值,判断当前这个状态下,两岸野人和牧师是否会安全存活
"""
if s.m > self.M or s.m < 0 or s.c > self.C or s.c < 0 or (s.m != 0 and s.m < s.c) or (
s.m != self.M and (self.M - s.m) < (self.C - s.c)): # 1.判断s.m是否合理,2.判断s.c是否合理,3.判断左岸情况是否安全,4.判断右岸情况是否安全
return False
else:
return True
# 启发函数
def hScore(self, s):
"""
:param s:当前状态
:return:当前状态的代价h=m+c-k*b 左岸人数越多,说明代价越大,越不好,同理如果在左岸就减少这个代价,在右岸的话,反而会增加这个代价
"""
return s.m + s.c - self.K * s.b
# def equal(self, a, b):
# """
# :param a:状态一
# :param b:状态二
# :return:bool,如果两个状态相等,则发挥bool
# """
# if a.data == b.data: # 判断两个状态是否一致,不用三个数值,而是用一个node,更快一些
# return True
# return False
#
# # 判断当前状态与父状态是否一致
# def back(self, new, s):
# if s.father is None:
# return False
# return self.equal(new, s.father)
# 扩展节点时在open表和closed表中找原来是否存在相同mcb属性的节点
def inList(self, newS):
"""
:param newS:待寻找到状态
:return:bool,state:表示在open表中是否出现过,如果出现过则返回这个状态,但是不返回原始的newS!
"""
for it in self.openList:
if it.data == newS.data:
return True, it
return False, None
def AStar(self): # s表示初始状态initial
"""
:return:bool,list:表示有没有解,如果有解,返回正排序后的答案序列
"""
self.openList.append(self.init) # 初始化open表
flag = 0 # 统计计算次数的
while self.openList: # open表非空
flag += 1
# print("open长度:" + str(len(self.openList)) + " " + str(flag))
nowS = self.openList.pop(0) # 取出open表第一个元素nowS
if nowS.data == self.goal.data: # 判断是否为目标节点,开始返回需要的解
resultAll = []
while nowS:
resultAll.append(nowS.data) # 递归向上寻找出口
nowS = nowS.father
resultAll.reverse() # 反转答案序列
return True, resultAll
self.closeList.add(self.getHash(nowS.data)) # 通过data中三个数字,将get加入closed表
# 以下得到一个get的新子节点new并考虑是否放入openlist
mMAX = nowS.m if nowS.b == 1 else (self.M - nowS.m) # 确定船的位置,得到上传传教士最大人数
cMAX = nowS.c if nowS.b == 1 else (self.C - nowS.c) # 确定船的位置,得到上传野人最大人数
for i in range(mMAX + 1): # 上船传教士,在合理范围内进行爆搜
for j in range(cMAX + 1): # 上船野人,暴力全部搜索一遍
if i + j == 0 or i + j > self.K or (i != 0 and i < j): # 船上非法情况,上船的人数=0;上船的人数大于载人数;若有传教士时,野人大于传教士人数
continue
if nowS.b == 1: # 当前船在左岸,下一状态统计船在右岸的情况
newS = State(nowS.m - i, nowS.c - j, 0) # 创造新的状态
else: # 当前船在右岸,下一状态统计船在左岸的情况
newS = State(nowS.m + i, nowS.c + j, 1) # 创造新的状态
# 优先级:not>and>ture。如果状态不安全或者要拓展的节点与当前节点的父节点状态一致。
if self.safe(newS): # 状态非法或new折返了 如果要拓展的节点满足以上情况,将它的父亲设为当前节点,计算f,并对open_list排序
newS.father = nowS
newS.steps = nowS.steps + 1 # 与起点的距离
newS.score = nowS.steps + self.hScore(nowS) # score = steps + h
tag, oldS = self.inList(newS)
if self.getHash(newS.data) in self.closeList: # 如果在close中发现这个状态,直接跳过,不考虑这个节点了
pass
elif tag: # 如果在open表中出现了这个状态,则需要进行有选择性的保留
if newS.score < oldS.score: # 如果新添加的状态代价会更加小(主要是steps会更小),那就把open中的移除,然后加上新的
self.openList.remove(oldS)
self.openList.append(newS)
the_key = operator.attrgetter('score') # 指定属性排序的key
self.openList.sort(key=the_key) # 按照键值排序
# print(newS.data)
else:
self.openList.append(newS) # 如果都没有的话,放入open表中
the_key = operator.attrgetter('score') # 指定属性排序的key
self.openList.sort(key=the_key)
# print(newS.data)
return False, []
GUI界面的显示:
# -*- coding: utf-8 -*-
# @Time : 2022-04-26 13:17
# @Site : 牧师-野人问题的解决方案
# @File : test2.py
# @Software: PyCharm
import sys
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
from PyQt5 import QtGui, QtCore
from MCK import *
from multiprocessing.pool import ThreadPool
from concurrent.futures import ThreadPoolExecutor, as_completed
import threading
class Example(QMainWindow):
ani1 = QtCore.pyqtSignal(int, int, list) # 用于线程之间交流的信号,控制船从左边到右边
ani0 = QtCore.pyqtSignal(int, int, list) # 用于线程之间交流的信号,控制船从右边到左边
def __init__(self):
super().__init__()
self.emptyType = QPixmap('')
self.backPhoto = QPixmap('river.jpg')
self.photoLabel = QLabel(self)
self.startButton = QPushButton("开始", self)
self.stopButton = QPushButton("暂停", self)
self.restartButton = QPushButton("恢复", self)
self.controLabel = QLabel("演示控制", self)
self.speedSlider = QSlider(Qt.Horizontal, self)
self.mNumLabel = QLabel("传教士:", self)
self.mNumInput = QLineEdit(self)
self.cNumLabe = QLabel("野 人:", self)
self.cNumInput = QLineEdit(self)
self.kNumLabe = QLabel("船容量:", self)
self.kNumInput = QLineEdit(self)
self.p1Label = QLabel("人", self)
self.p2Label = QLabel("人", self)
self.p3Label = QLabel("人", self)
self.sepaTag = QLabel(
"|\n|\n|\n|\n|\n|\n|\n|\n|\n|\n|\n|\n|\n|\n|\n|\n|\n|\n|\n|\n|\n|\n|\n|\n|\n|\n|\n|\n|\n|\n|\n|\n|\n|\n"
"|\n|\n|\n|\n|\n|\n|\n|\n|\n|\n|\n|\n|\n|\n|\n|\n|\n|\n|\n|\n|\n|\n|\n|\n|\n|\n|\n|\n",
self) # 用最傻的方式,画出分割框架:分割动画区域与控制台
self.speedLabel = QLabel("速度调节:(快->慢)", self)
self.leftM = QLabel("", self) # 设置左边的传教士的图片
self.leftMNum = QLabel("", self)
self.leftC = QLabel("", self) # 设置左边野人的图片
self.leftCNum = QLabel("", self)
self.rightC = QLabel("", self) # 设置右边野人的图片
self.rightMNum = QLabel("", self) # 设置右边的数量的标签
self.rightM = QLabel("", self) # 设置右边的传教士的图片
self.rightCNum = QLabel("", self) # 设置右边的数量标签
self.labelFont = QtGui.QFont()
self.inputFont = QtGui.QFont()
self.timeValue = 1000
self.pos1 = [400, 500]
self.midPos = [540, 520]
self.pos0 = [920, 700]
self.M = 0 # 传教士的总数
self.C = 0 # 野人的总数
self.K = 0 # 船的容量
self.ansList = [] # 存放结果
self.event = threading.Event() # 用于阻塞线程运行的event
self.pools = ThreadPoolExecutor(max_workers=100) # 线程池
self.stop = False # 是否按下暂停按钮
self.arrive = False
self.arriveEvent = threading.Event()
self.initUI()
def initUI(self):
self.labelFont.setFamily("SimHei") # 括号里可以设置成自己想要的其它字体
self.labelFont.setPointSize(12) # 括号里的数字可以设置成自己想要的字体大小
self.inputFont.setFamily("SimHei") # 括号里可以设置成自己想要的其它字体
self.inputFont.setPointSize(13) # 括号里的数字可以设置成自己想要的字体大小
self.mNumLabel.resize(150, 50)
self.mNumLabel.move(20, 50)
self.mNumLabel.setFont(self.labelFont)
self.mNumInput.resize(160, 44)
self.mNumInput.move(110, 54)
self.setFont(self.inputFont)
self.p1Label.resize(150, 50)
self.p1Label.move(280, 50)
self.p1Label.setFont(self.labelFont)
self.cNumLabe.resize(150, 50)
self.cNumLabe.move(20, 140)
self.cNumLabe.setFont(self.labelFont)
self.cNumInput.resize(160, 44)
self.cNumInput.move(110, 144)
self.setFont(self.inputFont)
self.p2Label.resize(150, 50)
self.p2Label.move(280, 140)
self.p2Label.setFont(self.labelFont)
self.kNumLabe.resize(150, 50)
self.kNumLabe.move(20, 220)
self.kNumLabe.setFont(self.labelFont)
self.kNumInput.resize(160, 44)
self.kNumInput.move(110, 224)
self.setFont(self.inputFont)
self.p3Label.resize(150, 50)
self.p3Label.move(280, 220)
self.p3Label.setFont(self.labelFont)
self.sepaTag.resize(30, 1000)
self.sepaTag.move(350, 0)
self.sepaTag.setFont(self.labelFont)
self.speedLabel.resize(200, 50)
self.speedLabel.move(70, 340)
self.speedLabel.setFont(self.labelFont)
self.speedSlider.resize(330, 50)
self.speedSlider.move(10, 400)
self.speedSlider.setMinimum(60) # 表示0.05s
self.speedSlider.setMaximum(250) # 表示1s的含义
self.speedSlider.setSingleStep(15)
self.speedSlider.setValue(150)
self.speedSlider.setTickPosition(QSlider.TicksBelow)
self.speedSlider.setTickInterval(1)
self.startButton.resize(100, 60)
self.startButton.move(120, 540)
self.startButton.setFont(self.inputFont)
self.controLabel.resize(200, 60)
self.controLabel.move(120, 700)
self.controLabel.setFont(self.labelFont)
self.stopButton.resize(100, 60)
self.stopButton.move(54, 780)
self.stopButton.setFont(self.inputFont)
self.restartButton.resize(100, 60)
self.restartButton.move(180, 780)
self.restartButton.setFont(self.inputFont)
# setStyleSheet("#MainWindow{background-color:green}")
self.photoLabel.setGeometry(360, 0, 1138, 1000)
width = self.backPhoto.width()
wRadio = self.photoLabel.width() / width
height = self.backPhoto.height()
hRadio = self.photoLabel.height() / height
new_img = self.backPhoto.scaled(int(width * wRadio), int(height * hRadio)) ##调整图片尺寸
self.photoLabel.setPixmap(new_img) ##在label上显示调整后的图片
self.leftM.setGeometry(580, 320, 200, 200)
Photo = QPixmap('m1.png')
width = Photo.width()
wRadio = self.leftM.width() / width
height = Photo.height()
hRadio = self.leftM.height() / height
newLeftM = Photo.scaled(int(width * wRadio), int(height * hRadio)) # 调整图片尺寸
self.leftM.setPixmap(newLeftM) # 在label上显示调整后的图片
self.leftM.setVisible(False)
self.leftMNum.setGeometry(720, 270, 100, 100)
self.leftMNum.setFont(self.labelFont)
self.leftC.setGeometry(500, 320, 200, 200)
Photo = QPixmap('c1.png')
width = Photo.width()
wRadio = self.leftC.width() / width
height = Photo.height()
hRadio = self.leftC.height() / height
newLeftC = Photo.scaled(int(width * wRadio), int(height * hRadio)) # 调整图片尺寸
self.leftC.setPixmap(newLeftC) # 在label上显示调整后的图片
self.leftC.setVisible(False)
self.leftCNum.setGeometry(560, 250, 100, 100)
self.leftCNum.setFont(self.labelFont)
self.rightM.setGeometry(1150, 750, 200, 200)
Photo = QPixmap('m0.png')
width = Photo.width()
wRadio = self.rightM.width() / width
height = Photo.height()
hRadio = self.rightM.height() / height
newRightM = Photo.scaled(int(width * wRadio), int(height * hRadio)) # 调整图片尺寸
self.rightM.setPixmap(newRightM) # 在label上显示调整后的图片
self.rightM.setVisible(False)
self.rightMNum.setGeometry(1195, 700, 100, 100)
self.rightMNum.setFont(self.labelFont)
self.rightC.setGeometry(1250, 750, 200, 200)
Photo = QPixmap('c0.png')
width = Photo.width()
wRadio = self.rightC.width() / width
height = Photo.height()
hRadio = self.rightC.height() / height
newRightC = Photo.scaled(int(width * wRadio), int(height * hRadio)) # 调整图片尺寸
self.rightC.setPixmap(newRightC) # 在label上显示调整后的图片
self.rightC.setVisible(False)
self.rightCNum.setGeometry(1375, 680, 100, 100)
self.rightCNum.setFont(self.labelFont)
self.speedSlider.valueChanged.connect(self.valueChange) # 绑定信号槽
self.startButton.clicked.connect(self.startClicked)
self.stopButton.clicked.connect(self.pauseClicked)
self.restartButton.clicked.connect(self.reStartClicked)
self.ani1.connect(self.leftToRight)
self.ani0.connect(self.rightToLeft)
self.setGeometry(300, 200, 1500, 1000)
self.setWindowTitle('传教士&野人问题工具')
self.show()
def valueChange(self): # 检测solider是否有数值上的变化
self.timeValue = self.speedSlider.value() * 10 # 转换单位
def leftToRight(self, mNum, cNum, nowS):
"""
:param mNum:船上的传教士数量
:param cNum:船上的野人的数量
:param nowS:现在的状态,主要是为了绘画出左岸和右岸的状态情况
:return:null,专门用于调度动画操作的函数
"""
# print("传教士数量:", mNum)
# print("野人数量:", cNum)
LboatLabel = QLabel(self)
LboatLabel.setGeometry(500, 500, 120, 120)
LboatMLabel = QLabel(self)
LboatMLabel.setGeometry(500, 500, 70, 70)
LboatMNumLabel = QLabel("", self)
LboatMNumLabel.setGeometry(400, 400, 100, 100)
LboatMNumLabel.setFont(self.labelFont)
LboatMNumLabel.setVisible(True)
LboatCLabel = QLabel(self)
LboatCLabel.setGeometry(500, 500, 70, 70)
LboatCNumLabel = QLabel("", self)
LboatCNumLabel.setGeometry(800, 800, 100, 100)
LboatCNumLabel.setFont(self.labelFont)
LboatCNumLabel.setVisible(True)
# 设置船的图片
boatPhoto = QPixmap('boat1.png')
width = boatPhoto.width()
wRadio = LboatLabel.width() / width
height = boatPhoto.height()
hRadio = LboatLabel.height() / height
newBoat = boatPhoto.scaled(int(width * wRadio), int(height * hRadio)) # 调整图片尺寸
LboatLabel.setPixmap(newBoat) # 在label上显示调整后的图片
LboatLabel.setVisible(True) # 在label上显示调整后的图片
# 设置船上的传教士的图片
if mNum <= 0: # 如果船上的传教士的人数为0,则不设置图片了
LboatMLabel.setVisible(False) # 在label上显示调整后的图片
else: # 如果船上的传教士的人数不为0,则设置图片
mPhoto = QPixmap('m1.png')
width = mPhoto.width()
wRadio = LboatMLabel.width() / width
height = mPhoto.height()
hRadio = LboatMLabel.height() / height
newMPhoto = mPhoto.scaled(int(width * wRadio), int(height * hRadio)) # 调整图片尺寸
LboatMLabel.setPixmap(newMPhoto) # 在label上显示调整后的图片
LboatMLabel.setVisible(True) # 在label上显示调整后的图片
# 设置船上传教士的人数
if mNum <= 0: # 如果船上的传教士的人数为0,则不设置人数了
LboatMNumLabel.setText("")
else: # 如果船上的传教士的人数不为0,设置人数
LboatMNumLabel.setText(str(mNum))
# 设置船上的野人的图片
if cNum <= 0: # 如果船上的野人的人数为0,则不设置图片了
LboatCLabel.setVisible(False) # 在label上显示调整后的图片
else: # 如果船上的野人的人数不为0,则设置图片
cPhoto = QPixmap('c1.png')
width = cPhoto.width()
wRadio = LboatCLabel.width() / width
height = cPhoto.height()
hRadio = LboatCLabel.height() / height
newCPhoto = cPhoto.scaled(int(width * wRadio), int(height * hRadio)) # 调整图片尺寸
LboatCLabel.setPixmap(newCPhoto) # 在label上显示调整后的图片
LboatCLabel.setVisible(True) # 在label上显示调整后的图片
# 设置穿上野人的数量
if cNum <= 0: # 如果船上的野人的人数为0,则不设置数量标签了
LboatCNumLabel.setText("")
else: # 如果船上的野人的人数不为0,则设置设置数量标签
LboatCNumLabel.setText(str(cNum))
anim1 = QtCore.QPropertyAnimation(LboatLabel, b'pos', self) # 设置动画的对象及其属性
anim1.setKeyValueAt(0, QPoint(self.pos1[0], self.pos1[1]))
anim1.setKeyValueAt(0.2, QPoint(self.midPos[0], self.midPos[1]))
anim1.setKeyValueAt(1, QPoint(self.pos0[0], self.pos0[1]))
anim1.setDuration(self.timeValue) # 设置动画间隔时间
anim2 = QtCore.QPropertyAnimation(LboatMLabel, b'pos') # 设置动画的对象及其属性
anim2.setKeyValueAt(0, QPoint(self.pos1[0] + 30, self.pos1[1] + 20))
anim2.setKeyValueAt(0.2, QPoint(self.midPos[0] + 30, self.midPos[1] + 20))
anim2.setKeyValueAt(1, QPoint(self.pos0[0] + 30, self.pos0[1] + 20))
anim2.setDuration(self.timeValue) # 设置动画间隔时间
anim3 = QtCore.QPropertyAnimation(LboatCLabel, b'pos') # 设置动画的对象及其属性
anim3.setKeyValueAt(0, QPoint(self.pos1[0] + 5, self.pos1[1] + 20))
anim3.setKeyValueAt(0.2, QPoint(self.midPos[0] + 5, self.midPos[1] + 20))
anim3.setKeyValueAt(1, QPoint(self.pos0[0] + 5, self.pos0[1] + 20))
anim3.setDuration(self.timeValue) # 设置动画间隔时间
anim4 = QtCore.QPropertyAnimation(LboatMNumLabel, b'pos') # 传教士人数动画设置动画的对象及其属性
anim4.setKeyValueAt(0, QPoint(self.pos1[0] + 75, self.pos1[1] - 40))
anim4.setKeyValueAt(0.2, QPoint(self.midPos[0] + 75, self.midPos[1] - 40))
anim4.setKeyValueAt(1, QPoint(self.pos0[0] + 75, self.pos0[1] - 40))
anim4.setDuration(self.timeValue) # 设置动画间隔时间
anim5 = QtCore.QPropertyAnimation(LboatCNumLabel, b'pos') # 野人人数动画设置动画的对象及其属性
anim5.setKeyValueAt(0, QPoint(self.pos1[0] + 20, self.pos1[1] - 40))
anim5.setKeyValueAt(0.2, QPoint(self.midPos[0] + 20, self.midPos[1] - 40))
anim5.setKeyValueAt(1, QPoint(self.pos0[0] + 20, self.pos0[1] - 40))
anim5.setDuration(self.timeValue) # 设置动画间隔时间
anims = QParallelAnimationGroup(self) # 制作动画组
anims.addAnimation(anim1)
anims.addAnimation(anim2)
anims.addAnimation(anim3)
anims.addAnimation(anim4)
anims.addAnimation(anim5)
# return anims
anims.start() # 启动动画
# time.sleep(self.timeValue / 1000)
# boatLabel.setVisible(False)
print(nowS)
print("向右开,右岸传教士人数:%d,野人人数:%d" % (self.M - nowS[0], self.C - nowS[1]))
anims.finished.connect(
lambda: self.setInVisible(0, LboatLabel, LboatMLabel, LboatMNumLabel, LboatCLabel, LboatCNumLabel,
self.M - nowS[0], self.C - nowS[1])) # 需要重新设置右岸的情况
return True
def rightToLeft(self, mNum, cNum, nowS):
# print("传教士数量:", mNum)
# print("野人数量:", cNum)
RboatLabel = QLabel(self)
RboatLabel.setGeometry(500, 500, 120, 120)
RboatMLabel = QLabel(self)
RboatMLabel.setGeometry(500, 500, 70, 70)
RboatMNumLabel = QLabel("", self)
RboatMNumLabel.setGeometry(400, 400, 100, 100)
RboatMNumLabel.setFont(self.labelFont)
RboatMNumLabel.setVisible(True)
RboatCLabel = QLabel(self)
RboatCLabel.setGeometry(500, 500, 70, 70)
RboatCNumLabel = QLabel("", self)
RboatCNumLabel.setGeometry(800, 800, 100, 100)
RboatCNumLabel.setFont(self.labelFont)
RboatCNumLabel.setVisible(True)
# 设置船的图片
boatPhoto = QPixmap('boat0.png')
width = boatPhoto.width()
wRadio = RboatLabel.width() / width
height = boatPhoto.height()
hRadio = RboatLabel.height() / height
newBoat = boatPhoto.scaled(int(width * wRadio), int(height * hRadio)) # 调整图片尺寸
RboatLabel.setPixmap(newBoat) # 在label上显示调整后的图片
RboatLabel.setVisible(True) # 在label上显示调整后的图片
# 设置船上的传教士的图片
if mNum <= 0: # 如果船上的传教士的人数为0,则不设置图片了
RboatMLabel.setVisible(False) # 在label上显示调整后的图片
else: # 如果船上的传教士的人数不为0,则不设置图片
mPhoto = QPixmap('m0.png')
width = mPhoto.width()
wRadio = RboatMLabel.width() / width
height = mPhoto.height()
hRadio = RboatMLabel.height() / height
newMPhoto = mPhoto.scaled(int(width * wRadio), int(height * hRadio)) # 调整图片尺寸
RboatMLabel.setPixmap(newMPhoto) # 在label上显示调整后的图片
RboatMLabel.setVisible(True) # 在label上显示调整后的图片
# 设置船上传教士的人数
if mNum <= 0: # 如果船上的传教士的人数为0,则不设置数量标签了
RboatMNumLabel.setText("")
else: # 如果船上的传教士的人数不为0,则设置数量标签
RboatMNumLabel.setText(str(mNum))
# 设置船上的野人的图片
if cNum <= 0: # 如果船上的野人的人数为0,则不设置图片了
RboatCLabel.setVisible(False) # 在label上显示调整后的图片
else: # 如果船上的野人的人数不为0,则设置图片
cPhoto = QPixmap('c0.png')
width = cPhoto.width()
wRadio = RboatCLabel.width() / width
height = cPhoto.height()
hRadio = RboatCLabel.height() / height
newCPhoto = cPhoto.scaled(int(width * wRadio), int(height * hRadio)) # 调整图片尺寸
RboatCLabel.setPixmap(newCPhoto) # 在label上显示调整后的图片
RboatCLabel.setVisible(True) # 在label上显示调整后的图片
# 设置穿上野人的数量
if cNum <= 0: # 如果船上的野人的人数为0,则不设置数量标签
RboatCNumLabel.setText("")
else: # 如果船上的野人的人数为0,则设置数量标签
RboatCNumLabel.setText(str(cNum))
anim1 = QtCore.QPropertyAnimation(RboatLabel, b'pos') # 设置动画的对象及其属性
anim1.setKeyValueAt(0, QPoint(self.pos0[0], self.pos0[1]))
anim1.setKeyValueAt(0.8, QPoint(self.midPos[0], self.midPos[1]))
anim1.setKeyValueAt(1, QPoint(self.pos1[0], self.pos1[1]))
anim1.setDuration(self.timeValue) # 设置动画间隔时间
anim2 = QtCore.QPropertyAnimation(RboatMLabel, b'pos') # 设置传教士的图片动画,设置动画的对象及其属性
anim2.setKeyValueAt(0, QPoint(self.pos0[0] + 63, self.pos0[1] + 20))
anim2.setKeyValueAt(0.8, QPoint(self.midPos[0] + 63, self.midPos[1] + 20))
anim2.setKeyValueAt(1, QPoint(self.pos1[0] + 63, self.pos1[1] + 20))
anim2.setDuration(self.timeValue) # 设置动画间隔时间
anim3 = QtCore.QPropertyAnimation(RboatCLabel, b'pos') # 设置野人的图片动画设置动画的对象及其属性
anim3.setKeyValueAt(0, QPoint(self.pos0[0] - 18, self.pos0[1] + 20))
anim3.setKeyValueAt(0.8, QPoint(self.midPos[0] - 18, self.midPos[1] + 20))
anim3.setKeyValueAt(1, QPoint(self.pos1[0] - 18, self.pos1[1] + 20))
anim3.setDuration(self.timeValue) # 设置动画间隔时间
anim4 = QtCore.QPropertyAnimation(RboatMNumLabel, b'pos') # 传教士人数动画设置动画的对象及其属性
anim4.setKeyValueAt(0, QPoint(self.pos0[0] + 75, self.pos0[1] - 40))
anim4.setKeyValueAt(0.8, QPoint(self.midPos[0] + 75, self.midPos[1] - 40))
anim4.setKeyValueAt(1, QPoint(self.pos1[0] + 75, self.pos1[1] - 40))
anim4.setDuration(self.timeValue) # 设置动画间隔时间
anim5 = QtCore.QPropertyAnimation(RboatCNumLabel, b'pos') # 野人人数动画设置动画的对象及其属性
anim5.setKeyValueAt(0, QPoint(self.pos0[0] + 20, self.pos0[1] - 40))
anim5.setKeyValueAt(0.8, QPoint(self.midPos[0] + 20, self.midPos[1] - 40))
anim5.setKeyValueAt(1, QPoint(self.pos1[0] + 20, self.pos1[1] - 40))
anim5.setDuration(self.timeValue) # 设置动画间隔时间
anims = QParallelAnimationGroup(self) # 设置并行的动画组
anims.addAnimation(anim1)
anims.addAnimation(anim2)
anims.addAnimation(anim3)
anims.addAnimation(anim4)
anims.addAnimation(anim5)
# return anims
anims.start() # 启动动画
# print("从右往左,传教士,船上%d,左岸%d" % (mNum, nowS[0]))
anims.finished.connect(
lambda: self.setInVisible(1, RboatLabel, RboatMLabel, RboatMNumLabel, RboatCLabel, RboatCNumLabel,
nowS[0], nowS[1])) # 需要重新设置左岸的情况
return True
def setInVisible(self, side, a, b, c, d, e, mNum, cNum):
"""
:param side:需要设置的河岸,1表示左岸,0表示右岸
:param a:想要设置为不可视的标签
:param b:想要设置为不可视的标签
:param c:想要设置为不可视的标签
:param d:想要设置为不可视的标签
:param e:想要设置为不可视的标签
:param mNum:对应河岸的传教士的人数
:param cNum:对应河岸的野人的人数
:return:null,
"""
a.setVisible(False)
b.setVisible(False)
c.setVisible(False)
d.setVisible(False)
e.setVisible(False)
if side == 1: # 到达的是左边,需要设置左边的人物
self.setLeft(mNum, cNum)
else: # 到达的是右边,需要设置右边的人物
self.setRight(mNum, cNum)
# 还原线程开始
self.arrive = True # 表示动画结束,船已经到达了
self.arriveEvent.set() # 重启阻塞事件arriveEvent
def startClicked(self):
"""
:return:bool,表示是否正常开始运行算法和动画,点击开始按钮,进行动画演示
"""
if self.ansList:
errorBox = QMessageBox(QMessageBox.Warning, '错误', '上个动画正在执行\n请稍等。')
errorBox.resize(60, 100)
errorBox.exec_()
return False
beginTime = time.time()
try:
self.M = int(self.mNumInput.text())
self.C = int(self.cNumInput.text())
self.K = int(self.kNumInput.text()) # 获取GUI界面上的数值
except:
errorBox = QMessageBox(QMessageBox.Warning, '错误', '输入数据不合法!\n请重新输入。')
errorBox.resize(60, 100)
errorBox.exec_()
return False
if (self.M < self.C): # 如果传教士小于野人,则第一步判断出,无解
errorBox = QMessageBox(QMessageBox.Warning, '错误', '该情况无解!\n请重新输入。')
errorBox.resize(60, 100)
errorBox.exec_()
return False
way = solution(self.M, self.C, self.K) # 输入三个最基本的参数,开始算法计算
tag, self.ansList = way.AStar()
if tag: # 使用算法计算,得到这个情况有解,并且求解出来结果
for a in self.ansList:
print(a)
tempTask = threading.Thread(target=self.animationsStart) # 开启线程,绘制动画
tempTask.start()
else:
errorBox = QMessageBox(QMessageBox.Warning, '错误', '该情况无解!\n请重新输入。')
errorBox.resize(60, 100)
errorBox.exec_()
return False
print("运行时间:", time.time() - beginTime) # 得到的是算法运行的时间,并不是动画运行的时间
return True
def setRight(self, mNum, cNum):
"""
:param mNum:右岸传教士的数量
:param cNum:右岸野人的数量
:return:null,设置GUI界面的图像,设置的时候,都遵循一个原则,不为0就设置,为0就不设置
"""
if mNum > 0:
self.rightM.setVisible(True)
self.rightMNum.setText(str(mNum))
else:
self.rightM.setVisible(False)
self.rightMNum.setText("")
if cNum > 0:
self.rightC.setVisible(True)
self.rightCNum.setText(str(cNum))
else:
self.rightC.setVisible(False)
self.rightCNum.setText("")
def setLeft(self, mNum, cNum):
"""
:param mNum:左岸传教士的数量
:param cNum:左岸野人的数量
:return:null,设置GUI界面的图像,设置的时候,都遵循一个原则,不为0就设置,为0就不设置
"""
if mNum > 0:
self.leftM.setVisible(True)
self.leftMNum.setText(str(mNum))
else:
self.leftM.setVisible(False)
self.leftMNum.setText("")
if cNum > 0:
self.leftC.setVisible(True)
self.leftCNum.setText(str(cNum))
else:
self.leftC.setVisible(False)
self.leftCNum.setText("")
def animationsStart(self): # 输入一个最终的方法解,进行动画演绎
"""
:return:null,放在线程内的动画绘制函数
"""
preS = self.ansList.pop(0)
self.setLeft(preS[0], preS[1])
self.setRight(self.M - preS[0], self.C - preS[1])
for nowS in self.ansList:
self.arrive = False
time.sleep(self.timeValue / 1000) # 下面有很多时间设置函数,都是为了动画可以合理的绘制出来
if (nowS[2] == 0): # 从左往右开船,目的地是b=0,右边
self.setLeft(nowS[0], nowS[1]) # 再设置开船之后的左岸情况(突出现在减少了穿上的人了)
time.sleep(self.timeValue / 4000)
self.ani1.emit(preS[0] - nowS[0], preS[1] - nowS[1], nowS) # 设置船上的人数情况,发送信号
# 在到达的时候,重新设置右岸的人数,也就是nowS的人数了
if not self.arrive:
self.arriveEvent.wait()
else: # 从右往左开船,目的地是b=1,左边
self.setRight(self.M - nowS[0], self.C - nowS[1]) # 再设置开船之后的右岸情况(突出现在减少了穿上的人了)
time.sleep(self.timeValue / 4000)
self.ani0.emit(nowS[0] - preS[0], nowS[1] - preS[1], nowS) # 从右向左开船,发送信号
# 在到达的时候,重新设置左岸的人数,也就是nowS的人数
if not self.arrive:
self.arriveEvent.wait()
time.sleep(self.timeValue / 2000)
preS = nowS
if self.stop: # 如果在中间出现了按下stop按键,则需要中断等待
self.event.wait()
self.setLeft(0, 0)
self.ansList = []
return True
def pauseClicked(self):
"""
:return:null,暂停按钮,设置是否停止
"""
self.stop = True
def reStartClicked(self):
"""
:return:null,恢复按钮,设置动画重新开始,并且event重新启动
"""
self.stop = False
self.event.set()
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())
图片如:(坚决抵制只放代码,不放图片的行为!)
所有的注释也挺清楚的,不懂可以多看注释。