FreeKill/lua/core/exppattern.lua

475 lines
11 KiB
Lua
Raw Permalink Normal View History

-- SPDX-License-Identifier: GPL-3.0-or-later
--[[
2023-04-12 20:51:09 +08:00
Exppattern
pattern Exppattern
2023-04-12 20:51:09 +08:00
pattern
1. (';') Matcher
2. Matcher ('|')
3. Matcher ','
Matcher ||||||id
2023-04-12 20:51:09 +08:00
'~' AJQK
2023-04-12 20:51:09 +08:00
slash,jink|2~4|spade;.|.|.|.|.|trick
使 '^' ^heart
','
^(heart, club)
]]--
---@class Matcher
---@field public trueName? string[]
---@field public number? integer[]
---@field public suit? string[]
---@field public place? string[]
---@field public name? string[]
---@field public cardType? string[]
---@field public id? integer[]
-- v0.2.6改动: cardType会被解析为trueName数组和name数组而不是自己单独成立
2024-10-24 21:57:26 +08:00
-- core改动 name数组为空时将根据trueName数组生成对应的name数组
local numbertable = {
["A"] = 1,
["J"] = 11,
["Q"] = 12,
["K"] = 13,
}
local placetable = {
[Card.PlayerHand] = "hand",
[Card.PlayerEquip] = "equip",
}
local card_type_table = {}
2024-10-24 21:57:26 +08:00
local card_truename_table = {}
local function fillCardTypeTable()
local tmp = {}
for _, cd in ipairs(Fk.cards) do
local t = cd:getTypeString()
local st = cd:getSubtypeString()
local tn = cd.trueName
-- TODO: local n = cd.name
if not tmp[tn] then
card_type_table[t] = card_type_table[t] or {}
card_type_table[st] = card_type_table[st] or {}
table.insertIfNeed(card_type_table[t], tn)
table.insertIfNeed(card_type_table[st], tn)
tmp[tn] = true
end
end
end
2024-10-24 21:57:26 +08:00
local function fillCardTrueNameTable()
local tmp = {}
for _, cd in ipairs(Fk.cards) do
local tn = cd.trueName
local n = cd.name
if not tmp[n] then
card_truename_table[tn] = card_truename_table[tn] or {}
table.insertIfNeed(card_truename_table[tn], n)
tmp[n] = true
end
end
end
local function matchSingleKey(matcher, card, key)
local match = matcher[key]
if not match then return true end
local val = card[key]
if key == "suit" then
val = card:getSuitString()
-- elseif key == "cardType" then
-- val = card:getTypeString()
elseif key == "place" then
val = placetable[Fk:currentRoom():getCardArea(card.id)]
if not val then
for _, p in ipairs(Fk:currentRoom().alive_players) do
val = p:getPileNameOfId(card.id)
if val then break end
end
end
end
if table.contains(match, val) then
return true
else
local neg = match.neg
if not neg then return false end
for _, t in ipairs(neg) do
if type(t) == "table" then
if not table.contains(t, val) then return true end
else
if t ~= val then return true end
end
end
end
return false
end
2023-02-26 16:51:29 +08:00
---@param matcher Matcher
---@param card Card
local function matchCard(matcher, card)
if type(card) == "number" then
card = Fk:getCardById(card)
end
return matchSingleKey(matcher, card, "trueName")
and matchSingleKey(matcher, card, "number")
and matchSingleKey(matcher, card, "suit")
and matchSingleKey(matcher, card, "place")
and matchSingleKey(matcher, card, "name")
-- and matchSingleKey(matcher, card, "cardType")
and matchSingleKey(matcher, card, "id")
end
local function hasNegIntersection(a, b)
-- 注意这里是拿a.neg和b比
local neg_pass = false
-- 第一次比较: 比较neg和正常值如有不同即认为可以匹配
-- 比如 ^jink 可以匹配 slash,jink
for _, neg in ipairs(a.neg or Util.DummyTable) do
for _, e in ipairs(b) do
if type(neg) == "table" then
neg_pass = not table.contains(neg, e)
else
neg_pass = neg ~= e
end
if neg_pass then return true end
end
end
-- 第二次比较: 比较双方neg
-- 比如 ^jink 可以匹配 ^slash
-- 没法比
end
local function hasIntersection(a, b)
if a == nil or b == nil or (#a + #b == 0) then
return true
end
local tmp = {}
for _, e in ipairs(a) do
tmp[e] = true
end
for _, e in ipairs(b) do
if tmp[e] then
return true
end
end
local neg_pass = hasNegIntersection(a, b) or hasNegIntersection(b, a)
return neg_pass
end
---@param a Matcher
---@param b Matcher
local function matchMatcher(a, b)
local keys = {
"trueName",
"number",
"suit",
"place",
"name",
-- "cardType",
"id",
}
for _, k in ipairs(keys) do
if not hasIntersection(a[k], b[k]) then
return false
end
end
return true
end
local function parseNegative(list)
local bracket = nil
local toRemove = {}
for i, element in ipairs(list) do
if element[1] == "^" or bracket then
list.neg = list.neg or {}
table.insert(toRemove, 1, i)
if element[1] == "^" and element[2] == "(" then
if bracket then
error("pattern syntax error. Cannot use nested bracket.")
else
bracket = {}
end
element = element:sub(3)
else
if element[1] == "^" then
element = element:sub(2)
end
end
local eofBracket
if element:endsWith(")") then
eofBracket = true
element = element:sub(1, -2)
end
if eofBracket then
if not bracket then
error('pattern syntax error. No matching bracket.')
else
table.insert(bracket, element)
table.insert(list.neg, bracket)
bracket = nil
end
else
if bracket then
table.insert(bracket, element)
else
table.insert(list.neg, element)
end
end
end
end
for _, i in ipairs(toRemove) do
table.remove(list, i)
end
end
local function parseNumToTable(from, dest)
for _, num in ipairs(from) do
if type(num) ~= "string" then goto continue end
local n = tonumber(num)
if not n then
n = numbertable[num]
end
if n then
table.insertIfNeed(dest, n)
else
if string.find(num, "~") then
local s, e = table.unpack(num:split("~"))
local start = tonumber(s)
if not start then
start = numbertable[s]
end
local _end = tonumber(e)
if not _end then
_end = numbertable[e]
end
for i = start, _end do
table.insertIfNeed(dest, i)
end
end
end
::continue::
end
end
local function parseRawNumTable(tab)
local ret = {}
parseNumToTable(tab, ret)
if tab.neg then
ret.neg = {}
parseNumToTable(tab.neg, ret.neg)
for _, t in ipairs(tab.neg) do
if type(t) == "table" then
local tmp = {}
parseNumToTable(t, tmp)
table.insert(ret.neg, tmp)
end
end
end
return ret
end
local function parseMatcher(str)
local t = str:split("|")
if #t < 7 then
for i = 1, 7 - #t do
table.insert(t, ".")
end
end
for i, item in ipairs(t) do
t[i] = item:split(",")
end
for _, list in ipairs(t) do
parseNegative(list)
end
local ret = {} ---@type Matcher
ret.trueName = not table.contains(t[1], ".") and t[1] or nil
if not table.contains(t[2], ".") then
ret.number = parseRawNumTable(t[2])
end
ret.suit = not table.contains(t[3], ".") and t[3] or nil
ret.place = not table.contains(t[4], ".") and t[4] or nil
2024-10-24 21:57:26 +08:00
if table.empty(card_truename_table) then
fillCardTrueNameTable()
end
-- ret.cardType = not table.contains(t[6], ".") and t[6] or nil
if table.empty(card_type_table) then
fillCardTypeTable()
end
for _, ctype in ipairs(t[6]) do
for _, n in ipairs(card_type_table[ctype] or Util.DummyTable) do
if not ret.trueName then ret.trueName = {} end
table.insertIfNeed(ret.trueName, n)
end
end
for _, neg in ipairs(t[6].neg or Util.DummyTable) do
if type(neg) ~= "table" then neg = { neg } end
if not ret.trueName then ret.trueName = {} end
if not ret.trueName.neg then ret.trueName.neg = {} end
local temp = {}
for _, ctype in ipairs(neg) do
table.insertTable(temp, card_type_table[ctype] or Util.DummyTable)
end
table.insert(ret.trueName.neg, temp)
end
2024-10-24 21:57:26 +08:00
if table.contains(t[5], ".") then
if ret.trueName then
ret.name = {}
for _, tn in ipairs(ret.trueName) do
table.insertTableIfNeed(ret.name, card_truename_table[tn] or Util.DummyTable)
end
for _, neg in ipairs(ret.trueName.neg or Util.DummyTable) do
if type(neg) ~= "table" then neg = { neg } end
if not ret.name.neg then ret.name.neg = {} end
local temp = {}
for _, tn in ipairs(neg) do
table.insertTableIfNeed(temp, card_truename_table[tn] or Util.DummyTable)
end
table.insert(ret.name.neg, temp)
end
else
ret.name = nil
end
else
ret.name = t[5]
end
if not table.contains(t[7], ".") then
ret.id = parseRawNumTable(t[7])
end
return ret
end
local function matcherKeyToString(tab)
if not tab then return "." end
local ret = table.concat(tab, ",")
if tab.neg then
for _, t in ipairs(tab.neg) do
if ret ~= "" then ret = ret .. "," end
if type(t) == "table" then
ret = ret .. ("^(" .. table.concat(t, ",") .. ")")
else
ret = ret .. "^" .. t
end
end
end
return ret
end
local function matcherToString(matcher)
return table.concat({
matcherKeyToString(matcher.trueName),
matcherKeyToString(matcher.number),
matcherKeyToString(matcher.suit),
matcherKeyToString(matcher.place),
matcherKeyToString(matcher.name),
matcherKeyToString(matcher.cardType),
matcherKeyToString(matcher.id),
}, "|")
end
---@class Exppattern: Object
---@field public matchers Matcher[]
local Exppattern = class("Exppattern")
function Exppattern:initialize(spec)
if not spec then
self.matchers = {}
elseif spec[1] ~= nil then
self.matchers = spec
else
self.matchers = {}
self.matchers[1] = spec
end
end
---@param pattern string
---@return Exppattern
function Exppattern:Parse(pattern)
error("This is a static method. Please use Exppattern:Parse instead")
end
function Exppattern.static:Parse(str)
local ret = Exppattern:new()
local t = str:split(";")
for i, s in ipairs(t) do
ret.matchers[i] = parseMatcher(s)
end
return ret
end
---@param card Card
function Exppattern:match(card)
for _, matcher in ipairs(self.matchers) do
local result = matchCard(matcher, card)
if result then
return true
end
end
return false
end
function Exppattern:matchExp(exp)
if type(exp) == "string" then
exp = Exppattern:Parse(exp)
end
local a = self.matchers
local b = exp.matchers
for _, m in ipairs(a) do
for _, n in ipairs(b) do
if matchMatcher(m, n) then
return true
end
end
end
return false
end
function Exppattern:__tostring()
local ret = ""
for i, matcher in ipairs(self.matchers) do
if i > 1 then ret = ret .. ";" end
ret = ret .. matcherToString(matcher)
end
return ret
end
return Exppattern