Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions paperback.lua
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ SMODS.load_file("utilities/misc_functions.lua")()
SMODS.load_file("utilities/ui.lua")()
SMODS.load_file("utilities/hooks.lua")()
SMODS.load_file("utilities/cross-mod.lua")()
SMODS.load_file("utilities/hopcroft_karp.lua")()

-- Load the atlases
SMODS.load_file("content/atlas.lua")()
Expand Down
133 changes: 133 additions & 0 deletions utilities/hopcroft_karp.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
INF = 2147483647
NIL = 0

--- Based on https://www.geeksforgeeks.org/dsa/hopcroft-karp-algorithm-for-maximum-matching-set-2-implementation/
--- Converted to Lua by your local Pretzal :)

--- A class to represent Bipartite graph for Hopcroft
--- Karp implementation
BipGraph = Object:extend()
--- Constructor
function BipGraph:init(m, n)
--- m and n are number of vertices on left
--- and right sides of Bipartite Graph
self.__m = m
self.__n = n
--- adj[u] stores adjacents of left side
--- vertex 'u'. The value of u ranges from 1 to m.
--- 0 is used for dummy vertex
self.__adj = {}
for k = 0, m do
self.__adj[k] = {}
end
end

--- To add edge from u to v and v to u
function BipGraph:addEdge(u, v)
self.__adj[u][#self.__adj[u] + 1] = v --- Add u to v’s list.
end

--- Returns true if there is an augmenting path, else returns
--- false
function BipGraph:bfs()
Q = {}
--- First layer of vertices (set distance as 0)
for u = 1, self.__m do
--- If this is a free vertex, add it to queue
if self.__pairU[u] == NIL then
--- u is not matched
self.__dist[u] = 0
Q[#Q + 1] = u
--- Else set distance as infinite so that this vertex
--- is considered next time
else
self.__dist[u] = INF
end
end
--- Initialize distance to NIL as infinite
self.__dist[NIL] = INF
--- Q is going to contain vertices of left side only.
while #Q > 0 do
--- Dequeue a vertex
u = table.remove(Q, 1)
--- If this node is not NIL and can provide a shorter path to NIL
if self.__dist[u] < self.__dist[NIL] then
--- Get all adjacent vertices of the dequeued vertex u

for _, v in pairs(self.__adj[u]) do
--- If pair of v is not considered so far
--- (v, pairV[V]) is not yet explored edge.
if self.__dist[self.__pairV[v]] == INF then
--- Consider the pair and add it to queue
self.__dist[self.__pairV[v]] = self.__dist[u] + 1
Q[#Q + 1] = self.__pairV[v]
end
end
end
end
--- If we could come back to NIL using alternating path of distinct
--- vertices then there is an augmenting path
return self.__dist[NIL] ~= INF
end

--- Returns true if there is an augmenting path beginning with free vertex u
function BipGraph:dfs(u)
if u ~= NIL then
--- Get all adjacent vertices of the dequeued vertex u
for _, v in pairs(self.__adj[u]) do
if self.__dist[self.__pairV[v]] == (self.__dist[u] + 1) then
--- If dfs for pair of v also returns true
if self:dfs(self.__pairV[v]) then
self.__pairV[v] = u
self.__pairU[u] = v
return true
end
end
end
--- If there is no augmenting path beginning with u.
self.__dist[u] = INF
return false
end
return true
end

function BipGraph:hopcroftKarp()
--- pairU[u] stores pair of u in matching where u
--- is a vertex on left side of Bipartite Graph.
--- If u doesn't have any pair, then pairU[u] is NIL
self.__pairU = {}
for k = 0, self.__m do
self.__pairU[k] = 0
end

--- pairV[v] stores pair of v in matching. If v
--- doesn't have any pair, then pairU[v] is NIL
self.__pairV = {}
for k = 0, self.__n do
self.__pairV[k] = 0
end

--- dist[u] stores distance of left side vertices
--- dist[u] is one more than dist[u'] if u is next
--- to u'in augmenting path
self.__dist = {}
for k = 0, self.__m do
self.__dist[k] = 0
end
--- Initialize result
local result = 0

--- Keep updating the result while there is an
--- augmenting path.
while self:bfs() do
--- Find a free vertex
for u = 1, self.__m do
--- If current vertex is free and there is
--- an augmenting path from current vertex
if self.__pairU[u] == NIL and self:dfs(u) then
result = result + 1
end
end
end
return result
end
56 changes: 16 additions & 40 deletions utilities/misc_functions.lua
Original file line number Diff line number Diff line change
Expand Up @@ -268,50 +268,26 @@ end
--- debuffed wild cards are considered their original suit only
---@return integer
function PB_UTIL.get_unique_suits(scoring_hand, bypass_debuff, flush_calc)
-- Set each suit's count to 0
local suits = {}

for k, _ in pairs(SMODS.Suits) do
suits[k] = 0
end

-- NOTE greedy algorithm is technically wrong for cards with weird suit combos,
-- for example a card with suit A+B might count for A, blocking another card
-- that can only be A
-- (a bipartite matching algorithm would work)

-- First we cover all the non Wild Cards in the hand
for _, card in ipairs(scoring_hand) do
if not SMODS.has_any_suit(card) then
for suit, count in pairs(suits) do
if card:is_suit(suit, bypass_debuff, flush_calc) and count == 0 then
suits[suit] = count + 1
break
end
local suit_count = 0
for _ in pairs(SMODS.Suits) do
suit_count = suit_count + 1
end
-- Initilize a bipartite matching algorithm because math is tight
local b = BipGraph(#scoring_hand, suit_count)

for card_index, card in ipairs(scoring_hand) do
local suit_index = 0
for suit, _ in pairs(SMODS.Suits) do
suit_index = suit_index + 1
if card:is_suit(suit, bypass_debuff, flush_calc) then
-- Add edges for each card based on suits
b:addEdge(card_index, suit_index)
end
end
end

-- Then we cover Wild Cards, filling the missing suits
for _, card in ipairs(scoring_hand) do
if SMODS.has_any_suit(card) then
for suit, count in pairs(suits) do
if card:is_suit(suit, bypass_debuff, flush_calc) and count == 0 then
suits[suit] = count + 1
break
end
end
end
end

-- Count the amount of suits that were found
local num_suits = 0

for _, v in pairs(suits) do
if v > 0 then num_suits = num_suits + 1 end
end

return num_suits
-- Gets maximum number of matches.
return b:hopcroftKarp()
end

--- Creates and opens the specified booster pack, the same way a Tag would do it
Expand Down