跳转至

在SelectPlay.lua中决定了要运行的脚本之后,脚本的实际执行转到Play.lua中在运行。下面就脚本运行过程进行简单介绍。

一些预备操作

首先,创建一个元组metaadd,并定义table的加法操作。

注:此处关于setmetatabla的使用以及元组这个数据类型可以参考https://www.runoob.com/lua/lua-metatables.html

metaadd = {}
function metaadd.__add(a, b)
    for _, value in ipairs(b) do
        table.insert(a, value)
    end
    return a
end
gPlay = setmetatable({}, metaadd)

接着,对一系列变量初始化。

--gRefPlayTable, gTestPlayTable, gNorPlayTable参见config中的table
gPlay = gPlay + gRefPlayTable + gTestPlayTable + gNorPlayTable

gPlayTable = {}
gTimeCounter = 0 --计算运行时间用于判断是否超时
gCurrentState = "" --当前状态机
gLastState  = "" --上一轮的状态机
gLastPlay = "" --上一轮的脚本,用于SelectPlay中
gCurrentPlay = "" --当前的脚本,用于SelectPlay中
gRealState = "" --真实的状态机
gLastCycle = 0 --上一轮的循环数
gLastRefMsg = "" --上一轮的裁判盒信息
gActiveRole = {} --场上active的球员
gIsOurIndirectKickExit = false --是敌方directkick

然后是一些工具函数

--分割字符串,可以理解为python中的类似操作
function split(inputstr, sep)
    if sep == nil then
        sep = "%s"
    end
    local t={}
    for str in string.gmatch(inputstr, "([^"..sep.."]+)") do
        table.insert(t, str)
    end
    return t
end

--计算一个table的长度
function table_length(t)
  local length=0
  for k, v in pairs(t) do
    length=length+1
  end
  return length
end
预备部分到此结束,下面开始核心内容

CreatePlay

gPlayTable.CreatePlay(spec)中的spec是一个类似于表的数据类型,此处的CreatePlay即具体脚本中的

gPlayTable.CreatePlay{
firstState = "start",

["start"] = {
  switch = function ()
    if ... then
      return ...
    end
  end,
  Leader   = task.goCmuRush(START_POS,math.pi,_,flag.allow_dss),
  ...
  match    = "[L][AS][MDBCF][EH]",
},
...

name = "Ref_KickOff_th",
applicable = {
  exp = "a",
  a = true
},
attribute = "attack",
timeout = 99999
}
在具体函数体中,首先声明spec中的组成部分合规:
    assert(spec.firstState ~= nil)
    assert(type(spec.name) == "string")
    assert(spec.applicable ~= nil)
    assert(spec.attribute ~= nil)
    assert(type(spec.timeout) == "number")
然后构造new_spec并初始化,等待加入gPlayTable
    new_spec = {}
    new_spec.firstState = spec.firstState
    new_spec.switch = spec.switch
    new_spec.name = spec.name
    new_spec.applicable = spec.applicable
    new_spec.attribute = spec.attribute
    new_spec.timeout = spec.timeout
接着对new_spec进行一些必要的处理
    for attr, attr_table in pairs(spec) do
        if attr~="applicable" and type(attr_table) == "table" then --此时attr_table即为状态机
            assert(attr_table.match ~= nil) --保证球员匹配不为nil
            new_attr_table = {}
            for rolename, roletask in pairs(attr_table) do
                if type(rolename) ~= "function" and rolename ~= "switch" and string.find(rolename,"_") then --若球员名称是用_分隔的,依次把task按顺序分配给各个球员角色
                    roles = split(rolename,"_")
                    assert(table_length(roles) == table_length(roletask)) --球员数与task数相等
                    for i=1,table_length(roles) do
                        new_attr_table[roles[i]] = roletask[i]
                    end
                else --否则直接添加球员-任务
                    new_attr_table[rolename] = roletask
                end
            end
            new_spec[attr] = new_attr_table
        end
    end
最后把new_spec加入gPlayTable
gPlayTable[new_spec.name] = new_spec

RunPlay

这里我们先跳过IsRoleActiveDoRolePosMatch,先来看一看在CreatePlay之后执行的RunPlay操作 function RunPlay(name)SelectPlay.lua的末尾执行,此处的name即为脚本名

首先,确保脚本在gPlayTable中存在(即CreatePlay过),否则输出错误信息

if(gPlayTable[name] == nil) then
    print("Error Play Name In RunPlay: "..name)
然后进行状态机的确定和相关操作
--在仿真界面上输出debug信息,指明运行的脚本
debugEngine:gui_debug_msg(CGeoPoint:new_local(-50, -param.pitchWidth/2-20),gCurrentPlay)

local curPlay = gPlayTable[name]
local curState
local isStateSwitched = false
--进行状态跳转,这里有if和else是因为有的脚本把switch写在状态里面,如上面的例子脚本;有的脚本把switch写在curPlay的第一层。
if curPlay.switch ~= nil then 
    curState = curPlay:switch()
else 
    if gCurrentState ~= "exit" and gCurrentState ~= "finish" then 
        curState = curPlay[gCurrentState]:switch()
    end
end
--设定状态机
if curState ~= nil then
    gLastState = gCurrentState
    gCurrentState = curState
    isStateSwitched = true
    world:SPlayFSMSwitchClearAll(true)
end
接着进行角色匹配,此处细节详见DoRolePosMatch的介绍

DoRolePosMatch(curPlay, false, isStateSwitched)

然后分配task

--Play中任务返回规则,即task.lua中的返回值,如
--return {mexe, mpos, kick.auto(role), idir, pre.middle, ikp, cp.toPlayer(role), flag.nothing}
--1 ---> task, 2 --> matchpos, 3--->kick, 4 --->dir, 5 --->pre, 6 --->kp, 7--->cp, 8--->flag

kickStatus:clearAll()--清空踢球状态
for rolename, task in pairs(curPlay[gRealState]) do
    if (type(task) == "function" and rolename ~= "match" and (gRoleNum[rolename] ~= nil or type(rolename)=="function")) then
    --若task是任务函数、且rolenname是球员角色名、且这名角色在gRoleNum中有车号分配(此处分配在DoRolePosMatch中执行)或rolename是一个会返回球员名的函数
        task = task(gRoleNum[rolename])
    end

    if (type(task) == "table" and rolename ~= "match" and (gRoleNum[rolename] ~= nil or type(rolename)=="function")) then
--若task是一个Table(即上面注释中说的return{...}),且rolenname是球员角色名、且这名角色在gRoleNum中有车号分配(此处分配在DoRolePosMatch中执行)或rolename是一个会返回球员名的函数
        if task[1] == nil then --若task[1]是nil,则给task赋值上一轮状态的task
            task = curPlay[gLastState][rolename]
        end

        --确定执行任务的车号,gRoleNum的细节详见RoleMatch.lua
        local roleNum
        if type(rolename)=="string" then
            roleNum = gRoleNum[rolename]

        elseif type(rolename)=="function" then
            roleNum = gRoleNum[rolename()]

        end

        --若匹配了场上的某号车
        if roleNum ~= -1 then
            --若返回的task的表中3号元素(即kick的状态)不为nil
            if task[3] ~= nil and task[3](roleNum) ~= kick.none() then
                local mkick = task[3](roleNum)--kick设定,平射or挑射
                local mdir = task[4](roleNum)--kick方向
                local mpre = task[5](roleNum)--kick精确度
                local mkp  = task[6](roleNum)--平射力度
                local mcp  = task[7](roleNum)--挑射力度
                local mflag = task[8]--flag
                local isDirOk = world:KickDirArrived(vision:Cycle(), mdir, mpre, roleNum)
                --若可以踢球了,或强制踢球,则setkick
                if isDirOk or bit:_and(mflag, flag.force_kick) ~= 0 then
                    if mkick == kick.flat() then
                        kickStatus:setKick(roleNum, mkp)
                    elseif mkick == kick.chip() then
                        kickStatus:setChipKick(roleNum, mcp)
                    end
                end
            end
            --print一些debug信息
            if(type(rolename)=="string") then
                debugEngine:gui_debug_msg(vision:OurPlayer(roleNum):Pos(), string.sub(rolename, 1, 1))
            end
            task[1](roleNum)--让roleNum号车执行task[1]中的task
        end
    end
end
最后进行一些计时的处理
gTimeCounter = gTimeCounter + 1
...
gLastCycle = vision:Cycle()

DoRolePosMatch

紧接上面函数,我们介绍一下Play中的角色匹配问题

DoRolePosMatch()函数在RunPlay中进行角色匹配任务,首先进行一些状态机的设定,防止在"exit"或"finish"

if gCurrentState == "exit" or gCurrentState == "finish" then
    gRealState = gLastState
else
    gRealState = gCurrentState
end
然后分配任务
    gActiveRole = {}
    for rolename, task in pairs(curPlay[gRealState]) do
        if(type(task) == "function" and rolename ~= "match" and rolename~="switch") then
            task = task()
        end
        if(type(task) == "table" and rolename ~= "match") then
            table.insert(gActiveRole, rolename) --把角色加入gActiveRole
            if task.name == "continue" and gCurrentState ~= "exit" and gCurrentState ~= "finish" then
                curPlay[gRealState][rolename] = {}
                for i,v in ipairs(curPlay[gLastState][rolename]) do
                    table.insert(curPlay[gRealState][rolename], v)
                end
                curPlay[gRealState][rolename].name = "continue"
            end
            if type(rolename) == "function" then
                gRolePos[rolename()] = curPlay[gRealState][rolename][2]()
            else
                gRolePos[rolename] = curPlay[gRealState][rolename][2]()
            end
        end
    end
最后进行一下角色的更新,这部分函数的具体内容详见RoleMatch.lua
UpdateRole(curPlay[gRealState].match, curPlay[gRealState].order, isPlaySwitched, isStateSwitched)

其它

Play.lua中还有一些次要函数,仅在此作简单介绍

  • ResetPlay(name):重新设置play,用于SelectPlay中

  • IsRoleActive(rolename):rolename在gActiveRole中

  • NeedExit(name): 状态进入"exit"or"finish",或timeout了,需要退出脚本,用于SelectPlay