FreeKill/lua/core/exppattern.lua

370 lines
8.4 KiB
Lua
Raw Normal View History

-- SPDX-License-Identifier: GPL-3.0-or-later
--[[
2023-04-12 12:51:09 +00:00
Exppattern
pattern Exppattern
2023-04-12 12:51:09 +00:00
pattern
1. (';') Matcher
2. Matcher ('|')
3. Matcher ','
2023-04-12 12:51:09 +00:00
Matcher ||||||id
'~' AJQK
2023-04-12 12:51:09 +00: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[]
local numbertable = {
["A"] = 1,
["J"] = 11,
["Q"] = 12,
["K"] = 13,
}
local placetable = {
[Card.PlayerHand] = "hand",
[Card.PlayerEquip] = "equip",
}
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 08:51:29 +00: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 hasIntersection(a, b)
if a == nil or b == nil 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
-- TODO: 判断含有neg的两个matcher
return false
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
ret.name = not table.contains(t[5], ".") and t[5] or nil
ret.cardType = not table.contains(t[6], ".") and t[6] or nil
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