diff --git a/dealer.lua b/dealer.lua new file mode 100644 index 0000000..9b3e642 --- /dev/null +++ b/dealer.lua @@ -0,0 +1,156 @@ +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 (nodds) 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 \ No newline at end of file