FreeKill/lua/core/exppattern.lua

270 lines
5.9 KiB
Lua

-- SPDX-License-Identifier: GPL-3.0-or-later
--[[
Exppattern 是一个用来描述卡牌的字符串。
pattern 字符串会被用来构建新的 Exppattern 对象,然后可以用它来检查一张牌。
pattern 字符串的语法:
1. 整个字符串可以被分号 (';') 切割,每一个分割就是一个 Matcher
2. 对于 Matcher 字符串,它是用 ('|') 分割的
3. 然后在 Matcher 的每一个细分中,又可以用 ',' 来进行更进一步的分割
其中 Matcher 的格式为 牌名|花色|点数|位置|详细牌名|类型|牌的id
更进一步,“点数” 可以用 '~' 符号表示数字的范围,并且可以用 AJQK 表示对应点数
例如:
slash,jink|2~4|spade;.|.|.|.|.|trick
]]--
---@class Matcher
---@field public name string[]
---@field public number integer[]
---@field public suit string[]
---@field public place string[]
---@field public generalName string[]
---@field public cardType string[]
---@field public id integer[]
local numbertable = {
["A"] = 1,
["J"] = 11,
["Q"] = 12,
["K"] = 13,
}
local suittable = {
[Card.Spade] = "spade",
[Card.Club] = "club",
[Card.Heart] = "heart",
[Card.Diamond] = "diamond",
}
local placetable = {
[Card.PlayerHand] = "hand",
[Card.PlayerEquip] = "equip",
}
local typetable = {
[Card.TypeBasic] = "basic",
[Card.TypeTrick] = "trick",
[Card.TypeEquip] = "equip",
}
---@param matcher Matcher
---@param card Card
local function matchCard(matcher, card)
if type(card) == "number" then
card = Fk:getCardById(card)
end
if matcher.name and not table.contains(matcher.name, card.name) and
not table.contains(matcher.name, card.trueName) then
return false
end
if matcher.number and not table.contains(matcher.number, card.number) then
return false
end
if matcher.suit and not table.contains(matcher.suit, card:getSuitString()) then
return false
end
if matcher.place and not table.contains(
matcher.place,
placetable[Fk:currentRoom():getCardArea(card.id)]
) then
local piles = table.filter(matcher.place, function(e)
return not table.contains(placetable, e)
end)
for _, pi in ipairs(piles) do
if ClientInstance then
if Self:getPileNameOfId(card.id) == pi then return true end
else
for _, p in ipairs(RoomInstance.alive_players) do
local pile = p:getPileNameOfId(card.id)
if pile == pi then return true end
end
end
end
return false
end
-- TODO: generalName
if matcher.cardType and not table.contains(matcher.cardType, typetable[card.type]) then
return false
end
if matcher.id and not table.contains(matcher.id, card.id) then
return false
end
return true
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
return false
end
---@param a Matcher
---@param b Matcher
local function matchMatcher(a, b)
local keys = {
"name",
"number",
"suit",
"place",
"generalName",
"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 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
local ret = {} ---@type Matcher
ret.name = not table.contains(t[1], ".") and t[1] or nil
if not table.contains(t[2], ".") then
ret.number = {}
for _, num in ipairs(t[2]) do
local n = tonumber(num)
if not n then
n = numbertable[num]
end
if n then
table.insertIfNeed(ret.number, 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(ret.number, i)
end
end
end
end
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.generalName = 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 = {}
for _, num in ipairs(t[6]) do
local n = tonumber(num)
if n and n > 0 then
table.insertIfNeed(ret.id, n)
end
end
end
return ret
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
return Exppattern