2023-04-09 13:35:35 +08:00
|
|
|
-- SPDX-License-Identifier: GPL-3.0-or-later
|
|
|
|
|
2023-01-16 19:13:07 +08:00
|
|
|
--[[
|
|
|
|
|
|
|
|
Exppattern is a string that describes cards of a same 'type', e.g. name,
|
|
|
|
suit, etc.
|
|
|
|
|
|
|
|
The string will be parsed and construct a new Exppattern instance.
|
|
|
|
Then we can use this instance to check the card.
|
|
|
|
|
|
|
|
Syntax for the string form:
|
|
|
|
1. the whole string can be splited by ';'. Every slice stands for a Matcher
|
|
|
|
2. For the matcher string, it can be splited by '|'.
|
|
|
|
3. And the arrays in class Match is concated by ',' in string.
|
|
|
|
|
|
|
|
Example:
|
|
|
|
slash,jink|2~4|spade;.|.|.|.|.|trick
|
|
|
|
|
|
|
|
]]--
|
|
|
|
|
|
|
|
---@class Matcher
|
2023-03-26 17:32:45 +08:00
|
|
|
---@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[]
|
2023-01-16 19:13:07 +08:00
|
|
|
|
|
|
|
local numbertable = {
|
|
|
|
["A"] = 1,
|
|
|
|
["J"] = 11,
|
|
|
|
["Q"] = 12,
|
|
|
|
["K"] = 13,
|
|
|
|
}
|
|
|
|
|
|
|
|
local suittable = {
|
|
|
|
[Card.Spade] = "spade",
|
|
|
|
[Card.Club] = "club",
|
|
|
|
[Card.Heart] = "heart",
|
|
|
|
[Card.Diamond] = "diamond",
|
|
|
|
}
|
|
|
|
|
2023-01-29 18:11:41 +08:00
|
|
|
local placetable = {
|
|
|
|
[Card.PlayerHand] = "hand",
|
|
|
|
[Card.PlayerEquip] = "equip",
|
|
|
|
}
|
|
|
|
|
2023-01-16 19:13:07 +08:00
|
|
|
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
|
|
|
|
|
2023-03-09 12:19:16 +08:00
|
|
|
if matcher.name and not table.contains(matcher.name, card.name) and
|
|
|
|
not table.contains(matcher.name, card.trueName) then
|
2023-01-16 19:13:07 +08:00
|
|
|
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
|
|
|
|
|
2023-01-29 18:11:41 +08:00
|
|
|
if matcher.place and not table.contains(
|
|
|
|
matcher.place,
|
|
|
|
placetable[Fk:currentRoom():getCardArea(card.id)]
|
|
|
|
) then
|
2023-03-07 10:21:56 +08:00
|
|
|
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
|
2023-03-05 01:28:59 +08:00
|
|
|
end
|
|
|
|
end
|
2023-01-29 18:11:41 +08:00
|
|
|
return false
|
|
|
|
end
|
2023-03-05 01:28:59 +08:00
|
|
|
|
2023-01-16 19:13:07 +08:00
|
|
|
-- TODO: generalName
|
2023-02-26 16:51:29 +08:00
|
|
|
|
2023-01-16 19:13:07 +08:00
|
|
|
if matcher.cardType and not table.contains(matcher.cardType, typetable[card.type]) then
|
|
|
|
return false
|
|
|
|
end
|
2023-02-26 16:51:29 +08:00
|
|
|
|
2023-01-16 19:13:07 +08:00
|
|
|
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
|
2023-01-29 18:11:41 +08:00
|
|
|
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
|
|
|
|
|
2023-01-16 19:13:07 +08:00
|
|
|
for i = start, _end do
|
2023-01-29 18:11:41 +08:00
|
|
|
table.insertIfNeed(ret.number, i)
|
2023-01-16 19:13:07 +08:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2023-01-29 18:11:41 +08:00
|
|
|
ret.suit = not table.contains(t[3], ".") and t[3] or nil
|
2023-03-07 10:21:56 +08:00
|
|
|
ret.place = not table.contains(t[4], ".") and t[4] or nil
|
2023-01-16 19:13:07 +08:00
|
|
|
ret.generalName = not table.contains(t[5], ".") and t[5] or nil
|
2023-01-29 18:11:41 +08:00
|
|
|
ret.cardType = not table.contains(t[6], ".") and t[6] or nil
|
2023-01-16 19:13:07 +08:00
|
|
|
|
|
|
|
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
|
2023-03-26 17:32:45 +08:00
|
|
|
---@field public matchers Matcher[]
|
2023-01-16 19:13:07 +08:00
|
|
|
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
|
|
|
|
|
2023-03-26 17:32:45 +08:00
|
|
|
---@param pattern string
|
|
|
|
---@return Exppattern
|
|
|
|
function Exppattern:Parse(pattern)
|
|
|
|
error("This is a static method. Please use Exppattern:Parse instead")
|
|
|
|
end
|
|
|
|
|
2023-01-16 19:13:07 +08:00
|
|
|
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
|