fortunes_foundation/dealer.lua
2024-02-07 20:59:31 -08:00

156 lines
3.5 KiB
Lua

function deal(ruleset)
local n_usable_cards=ruleset.n_usable_cards
local n_final_slots=ruleset.n_slots-1
local n_usable_slots=n_final_slots+1
local tower_height = n_usable_cards\n_final_slots
-- prototype
local deal1,generate_wells,generate_pops,accepts
local deal1=function()
local slots,max_height={},{}
for i=0,n_final_slots do
slots[i]={}
max_height[i]=tower_height
end
max_height[0]=1
local wells=generate_wells()
local pops=generate_pops(wells)
local function pek(lst)
return lst[#lst]
end
local function pop(lst)
return deli(lst,#lst)
end
local function pop_accepted_card(lst)
local card=lst[#lst]
if (card and accepts(lst[#lst-1],card)) return pop(lst)
end
local function find_home(odds,card,source,exclude)
assert(card!=0)
local acceptors={}
local start_points={}
for s=0,#slots do
local n=#slots[s]
if s==exclude then
-- don't place it here ever
elseif ruleset.deck.instantly_accepted[card] and n==max_height[s]-1 then
-- can't place an instantly accepted card at the bottom of a slot
else
if (accepts(pek(slots[s]),card)) add(acceptors,s)
if (n<max_height[s]) add(start_points,s)
end
end
local acceptor=rnd(acceptors)
local start_point=rnd(start_points)
if (acceptor and rnd()>odds) acceptor=nil
local a=acceptor or start_point or source
if (a) add(slots[a],card) return true
end
local original_pops=#pops
while #pops>0 do
local w=deli(pops)
assert(w!=0)
local exclude
-- unblock aux slot if this is not arcana
if w <= ruleset.n_suits then
local c=pop(slots[0])
assert(c!=0)
if (c and not find_home(1.0,c,nil,0)) return
exclude = 0
end
local card=pop(wells[w])
assert(card!=0)
if (not find_home(1.0,card,nil,exclude)) return
local n_moves=48
for i=1,n_moves do
local src=flr(rnd()*(#slots+1))
local card=pop_accepted_card(slots[src])
if card then
local odds=0.0
if (not find_home(odds,card,src,nil)) return
end
end
end
-- fix any stacks that are too tall
max_height[0]=0 -- auxiliary slot must be empty
for i=0,#slots do
while #slots[i]>max_height[i] do
local card=pop(slots[i])
if (not find_home(0.0,card,nil,nil)) return
end
end
-- get rid of auxiliary slot
slots[0]=nil
for s=1,#slots do
if (ruleset.deck.instantly_accepted[pek(slots[s])]) return
assert(#slots[s]==tower_height)
end
return slots
end
generate_wells=function()
local split_point=flr(rnd()*(ruleset.n_arcana+1))
local wells={}
for i=1,ruleset.n_suits+2 do
wells[i]={}
end
for r=2,ruleset.n_cards_per_suit do
for s=1,ruleset.n_suits do
local card=ruleset.n_cards_per_suit * (s - 1) + (r - 1) + 1
assert(card!=0)
add(wells[s],card)
end
end
local first_arcana=ruleset.n_suits*ruleset.n_cards_per_suit+1
local arcana0=ruleset.n_suits
local arcana1=arcana0+1
for r=0,split_point-1 do
assert(first_arcana+r!=0)
add(wells[arcana0],first_arcana+r)
end
for r=ruleset.n_arcana-1,split_point,-1 do
assert(first_arcana+r!=0)
add(wells[arcana1],first_arcana+r)
end
return wells
end
generate_pops=function(wells)
local pops={}
for w=1,#wells do
for _=1,#wells[w] do
add(pops,w)
end
end
shuf(pops)
return pops
end
accepts=function(c0,c1)
assert(c0!=0)
assert(c1!=0)
assert(c1!=nil)
if (c0==nil) return true
c0=ruleset.deck.cards[c0]
c1=ruleset.deck.cards[c1]
return c0.suit==c1.suit and (c1.rank == c0.rank+1 or c0.rank==c1.rank+1)
end
while true do
local d=deal1()
if (d) return d
end
end