fortunes_foundation/board.lua
2024-02-12 15:03:55 -08:00

264 lines
6.0 KiB
Lua

board=klass()
function board:init(w)
local ruleset=w.ruleset
self.watcher=w
self.ruleset=ruleset
self.cursor=cursor:new(self)
self.animator=animator:new()
self.checkpoint=nil
self.slots={}
self.wells={}
self.restart_progress=0
-- board slots
-- ...n_slots: normal
for i=1,ruleset.n_slots do
add(self.slots,slot:new(ruleset))
end
-- n_slots+1: special
add(self.slots,slot:new(ruleset,1))
-- wells:
-- ...n_suits: wands, cups, swords, pentacles, etc
local function add_suit(_self,suit)
local w=well:new(_self.ruleset, function(lst,new)
assert(lst) -- the ace is always present initially
if (new.suit!=suit) return
return new.suit==suit and new.rank==lst.rank+1
end)
w.obscured_by_extra_slot=true
add(_self.wells, w)
end
for suit in all(self.ruleset.deck.suits) do
add_suit(self,suit)
end
-- n_suits+1: arcana ascending
add(self.wells,well:new(self.ruleset,function(lst,new)
if (new.suit!='a') return
if (not lst) return new.rank==0
return new.rank==lst.rank+1
end))
self.wells[#self.wells].obscured_by_last_well=true
-- n_suits+2: arcana descending
add(self.wells,well:new(self.ruleset,function(lst,new)
if (new.suit!='a') return
if (not lst) return new.rank==self.ruleset.n_arcana-1
return new.rank==lst.rank-1
end))
self.wells[#self.wells].obscured_by_last_well=true
-- top arcana
add(self.wells,well:new(self.ruleset,function(lst,new)
local w1=self.wells[self.ruleset.n_suits+1]
local w2=self.wells[self.ruleset.n_suits+2]
if (#w1.contents + #w2.contents != self.ruleset.n_arcana-1) return
return new.suit=='a'
end))
self:deal(w.seed or seeds:choose(self.ruleset.pool))
end
function board:get_endgame_card()
local last_arcana=self.wells[#self.wells]:peek()
if (last_arcana) return last_arcana
return self.last_card or 1
end
function board:deal(seed)
local deal=deal(self.ruleset,seed)
local n_usable_slots=self.ruleset.n_slots - 1
for i=1,#self.ruleset.deck.aces do
local well=self.wells[i]
local ace=self.ruleset.deck.aces[i]
self:animate_move_ace_to_well(ace,i)
end
local rows=#deal[1]
for y=1,rows do
for x=1,#deal do
local outx=x
if (x>n_usable_slots\2) outx+=1
self:animate_move_new_card_to_slot(deal[x][y],outx)
end
end
end
function board:set_restart_progress(progress)
self.restart_progress=progress
end
function board:get_completion_level()
return self.ruleset.completion_level
end
function board:undo()
if (not self.checkpoint) return
if (not self.watcher:intercept("undo")) sounds:dire() return
sounds:menu()
self.checkpoint:apply(self)
self.checkpoint=nil
end
function board:on_idle()
self:find_automove()
end
function board:pre_move(card)
self.checkpoint=checkpoint:new(self,card)
end
function board:on_move()
self:find_automove()
end
function board:is_won()
if (not self:can_take_input()) return false
for s=1,#self.slots do
if (self.slots[s]:peek()) return false
end
return true
end
function board:find_automove()
for s=1,#self.slots do
local top=self.slots[s]:peek()
if top then
for w=#self.wells,1,-1 do
if w<=self.ruleset.n_suits and self.slots[self.ruleset.n_slots+1]:peek()!=nil then
-- the top wells are blocked
elseif self.wells[w]:would_accept(top) then
self:animate_and_move_to_well(s,w)
self.last_card=top
return true
end
end
end
end
end
function board:can_take_input()
return self.animator:idle()
end
function board:update()
local was_idle=self.animator:idle()
self.animator:update()
local is_idle=self.animator:idle()
if (not was_idle and is_idle) then
self:on_idle()
end
if (is_idle) self.cursor:update()
end
function board:draw()
local extra_slot_full=self.slots[self.ruleset.n_slots+1]:peek()!=nil
local last_well_full=self.wells[#self.wells]:peek()!=nil
for w_ix=1,#self.wells do
local w=self.wells[w_ix]
local l=self.ruleset.layouts:well(w_ix)
local shadowed=nil
if w.obscured_by_last_well and last_well_full then
shadowed=true
end
if w.obscured_by_extra_slot and extra_slot_full then
shadowed=true
end
for i=1,#w.contents do
local x,y=l:place_card(i)
self.ruleset.deck:draw_card(x,y,w.contents[i],{rotate=l.rotated,shadowed=shadowed})
end
end
local function forall_slots(cb)
for s_ix=1,#self.slots do
local s=self.slots[s_ix]
local l=self.ruleset.layouts:slot(s_ix)
local n=#s.contents
if (self.cursor.grabbed==s_ix) n-=1
cb(x,y,s_ix,s,l,n)
end
end
forall_slots(function(x,y,s_ix,s,l,n)
for i=1,n do
local x,y=l:place_card(i)
self.ruleset.deck:draw_card(x,y,s.contents[i],{rotate=l.rotated})
end
end)
if self.checkpoint then
local cpl=self.ruleset.layouts:checkpoint()
local x,y=cpl:place_card(0)
self.ruleset.deck:draw_card(x,y,self.checkpoint.card,{shadowed=true})
end
self.animator:draw()
if self.animator:idle() then
self.watcher:draw(self)
local hover_slot=self.cursor:hover_slot()
forall_slots(function(x,y,s_ix,s,l,n)
if hover_slot==s_ix then
self.cursor:draw_at(l,n)
end
end)
end
democrap:distort_screen(self.restart_progress)
end
slot=klass()
function slot:init(ruleset,max_n)
self.ruleset=ruleset
self.contents={}
self.max_n=max_n
end
function slot:would_accept(new)
local n=#self.contents
if (n==self.max_n) return
local lst=self.contents[n]
if (not lst) return true
lst=self.ruleset.deck.cards[lst]
new=self.ruleset.deck.cards[new]
return lst.suit==new.suit and (lst.rank==new.rank-1 or lst.rank==new.rank+1)
end
function slot:add(card)
add(self.contents,card)
end
function slot:peek()
return self.contents[#self.contents]
end
function slot:pop()
return deli(self.contents,#self.contents)
end
well=klass()
function well:init(ruleset,accept_cb)
self.ruleset=ruleset
self.accept_cb=accept_cb
self.contents={}
end
function well:would_accept(new)
local lst=self.contents[#self.contents]
if (lst) lst=self.ruleset.deck.cards[lst]
assert(new,"must be populated")
new=self.ruleset.deck.cards[new]
assert(new,"must be populated")
return self.accept_cb(lst,new)
end
function well:add(card)
add(self.contents,card)
end
function well:clear()
self.contents={}
end
function well:peek()
return self.contents[#self.contents]
end