Table of Contents
计划执行器(Plan Runner)
从规划器得到解决方案后,会尝试按顺序执行每个行动,并把行动效果应用到世界状态上。如果任务执行失败,计划也会失败,并且强制规划器重新规划。因为在实际执行过程中,世界状态不在是静态的,收诸多因素的影响,可能会发生变化,导致计划中的某项行动的前提条件不在满足,从而任务失败。
简单的实现,暂不考虑其它功能
require "HTN.Core.Planner"
local PlanRunner = {}
function PlanRunner:DoPlan (_ws,_plan,_planer)
for i, a in ipairs(_plan) do
ApplyActionEffects(_ws,a)
end
--删除执行完毕后的世界状态
PrintWorldState(_ws,0)
end
return PlanRunner
实际执行计划的过程中,需要考虑行动的执行条件是否满足,因为世界状态不像规划过程时那样是静态,相反,是无时无刻发生变化的,而且需要在执行失败后重新进行规划。
另外,行动的执行不是即时完成的,有些行动需要花时间完成,如移动,动画播放等。
还有就是可以给行动设置最大执行时间,超过执行时间则意味着执行失败。
因此,执行器的进一步实现是,变成异步响应。
改进后,变为由行动自主通知,行动执行完毕后调用End就可以
local PlanRunner = {}
function PlanRunner:DoPlan (_ws,_plan,_planer)
self.Plan = _plan
self.WS = _ws
self:DoAction(1)
end
function PlanRunner:DoAction(_index)
local a = self.Plan[_index]
if IsFitPreconds(self.WS,a.preconds) then
self.curIndex = _index
print("执行行为"..a.name)
--执行完毕后回调 FinishAction
a.End = function()
self:FinishAction(_index)
end
a:Execute(self.WS)
--可以添加到计时队列,判断是否超时,来终止计划
else
print("条件不满足,计划终止")
return
end
end
function PlanRunner:FinishAction(_index)
local a = self.Plan[_index]
ApplyActionEffects(self.WS,a)
if _index == #self.Plan then
-- 完成最后一个任务
PrintWorldState(self.WS,0)
else
-- 开始下一个任务
self:DoAction(_index + 1)
end
end
return PlanRunner
Action的定义也做一些小改动
Actions.Move = function(...)
local input = {...}
local env = setmetatable(
{
f = input[1],
t = input[2]
},
{__index = _G}
)
local action = {}
action.Effects = {
positive = {
Substitute("BoatAt([t])",env)
},
negative = {
Substitute("BoatAt([f])",env)
}
}
function action:Execute(_ws)
-- TODO 实现
self.End()
end
return action
end
打印输出解决方案的执行结果
----当前世界状态 { BoatAt LocA ,LocA.M = 3,LocA.C = 3 ,LocB.M = 0,LocB.C = 0 }
解决方案步数 33
执行行为Load(LocA,1,1)
----当前世界状态 { BoatAt LocA ,LocA.M = 2,LocA.C = 2 ,LocB.M = 0,LocB.C = 0 }
执行行为Move(LocA,LocB)
----当前世界状态 { BoatAt LocB ,LocA.M = 2,LocA.C = 2 ,LocB.M = 0,LocB.C = 0 }
执行行为UnLoad(LocB,1,1)
----当前世界状态 { BoatAt LocB ,LocA.M = 2,LocA.C = 2 ,LocB.M = 1,LocB.C = 1 }
执行行为Load(LocB,1,0)
----当前世界状态 { BoatAt LocB ,LocA.M = 2,LocA.C = 2 ,LocB.M = 0,LocB.C = 1 }
执行行为Move(LocB,LocA)
----当前世界状态 { BoatAt LocA ,LocA.M = 2,LocA.C = 2 ,LocB.M = 0,LocB.C = 1 }
执行行为UnLoad(LocA,1,0)
----当前世界状态 { BoatAt LocA ,LocA.M = 3,LocA.C = 2 ,LocB.M = 0,LocB.C = 1 }
执行行为Load(LocA,1,1)
----当前世界状态 { BoatAt LocA ,LocA.M = 2,LocA.C = 1 ,LocB.M = 0,LocB.C = 1 }
执行行为Move(LocA,LocB)
----当前世界状态 { BoatAt LocB ,LocA.M = 2,LocA.C = 1 ,LocB.M = 0,LocB.C = 1 }
执行行为UnLoad(LocB,1,1)
----当前世界状态 { BoatAt LocB ,LocA.M = 2,LocA.C = 1 ,LocB.M = 1,LocB.C = 2 }
执行行为Load(LocB,1,0)
----当前世界状态 { BoatAt LocB ,LocA.M = 2,LocA.C = 1 ,LocB.M = 0,LocB.C = 2 }
执行行为Move(LocB,LocA)
----当前世界状态 { BoatAt LocA ,LocA.M = 2,LocA.C = 1 ,LocB.M = 0,LocB.C = 2 }
执行行为UnLoad(LocA,1,0)
----当前世界状态 { BoatAt LocA ,LocA.M = 3,LocA.C = 1 ,LocB.M = 0,LocB.C = 2 }
执行行为Load(LocA,2,0)
----当前世界状态 { BoatAt LocA ,LocA.M = 1,LocA.C = 1 ,LocB.M = 0,LocB.C = 2 }
执行行为Move(LocA,LocB)
----当前世界状态 { BoatAt LocB ,LocA.M = 1,LocA.C = 1 ,LocB.M = 0,LocB.C = 2 }
执行行为UnLoad(LocB,2,0)
----当前世界状态 { BoatAt LocB ,LocA.M = 1,LocA.C = 1 ,LocB.M = 2,LocB.C = 2 }
执行行为Load(LocB,1,1)
----当前世界状态 { BoatAt LocB ,LocA.M = 1,LocA.C = 1 ,LocB.M = 1,LocB.C = 1 }
执行行为Move(LocB,LocA)
----当前世界状态 { BoatAt LocA ,LocA.M = 1,LocA.C = 1 ,LocB.M = 1,LocB.C = 1 }
执行行为UnLoad(LocA,1,1)
----当前世界状态 { BoatAt LocA ,LocA.M = 2,LocA.C = 2 ,LocB.M = 1,LocB.C = 1 }
执行行为Load(LocA,2,0)
----当前世界状态 { BoatAt LocA ,LocA.M = 0,LocA.C = 2 ,LocB.M = 1,LocB.C = 1 }
执行行为Move(LocA,LocB)
----当前世界状态 { BoatAt LocB ,LocA.M = 0,LocA.C = 2 ,LocB.M = 1,LocB.C = 1 }
执行行为UnLoad(LocB,2,0)
----当前世界状态 { BoatAt LocB ,LocA.M = 0,LocA.C = 2 ,LocB.M = 3,LocB.C = 1 }
执行行为Load(LocB,1,0)
----当前世界状态 { BoatAt LocB ,LocA.M = 0,LocA.C = 2 ,LocB.M = 2,LocB.C = 1 }
执行行为Move(LocB,LocA)
----当前世界状态 { BoatAt LocA ,LocA.M = 0,LocA.C = 2 ,LocB.M = 2,LocB.C = 1 }
执行行为UnLoad(LocA,1,0)
----当前世界状态 { BoatAt LocA ,LocA.M = 1,LocA.C = 2 ,LocB.M = 2,LocB.C = 1 }
执行行为Load(LocA,1,1)
----当前世界状态 { BoatAt LocA ,LocA.M = 0,LocA.C = 1 ,LocB.M = 2,LocB.C = 1 }
执行行为Move(LocA,LocB)
----当前世界状态 { BoatAt LocB ,LocA.M = 0,LocA.C = 1 ,LocB.M = 2,LocB.C = 1 }
执行行为UnLoad(LocB,1,1)
----当前世界状态 { BoatAt LocB ,LocA.M = 0,LocA.C = 1 ,LocB.M = 3,LocB.C = 2 }
执行行为Load(LocB,1,0)
----当前世界状态 { BoatAt LocB ,LocA.M = 0,LocA.C = 1 ,LocB.M = 2,LocB.C = 2 }
执行行为Move(LocB,LocA)
----当前世界状态 { BoatAt LocA ,LocA.M = 0,LocA.C = 1 ,LocB.M = 2,LocB.C = 2 }
执行行为UnLoad(LocA,1,0)
----当前世界状态 { BoatAt LocA ,LocA.M = 1,LocA.C = 1 ,LocB.M = 2,LocB.C = 2 }
执行行为Load(LocA,1,1)
----当前世界状态 { BoatAt LocA ,LocA.M = 0,LocA.C = 0 ,LocB.M = 2,LocB.C = 2 }
执行行为Move(LocA,LocB)
----当前世界状态 { BoatAt LocB ,LocA.M = 0,LocA.C = 0 ,LocB.M = 2,LocB.C = 2 }
执行行为UnLoad(LocB,1,1)
----当前世界状态 { BoatAt LocB ,LocA.M = 0,LocA.C = 0 ,LocB.M = 3,LocB.C = 3 }
从结果来看,先不看结果是否正确,可以发现TFD算法存在一些缺陷。就和寻路算法一样,简单的前向搜索可以保证得到解决方案,但不保证是最优,而且,往往是比较长(差)的结果。针对本次测试,得到的解决方案一共有33步,除以3 也就是11个Transport任务(Transport任务可以分解为3个基元任务,也就是行动)。
下一步,对规划器进行优化,使用一些启发算法来进行优化,减少无用计算,看看可不可以减少一下步数。
学习资料
开始时打算使用RxLua来做的,但是由于需要花挺多时间和精力去研究,所以就暂时打消这个念头,采用一些简单粗暴的方法。不过有时简单粗暴并不意味着差,反而比较清晰了高效。