Multiple rulesets

This commit is contained in:
Pyrex 2024-02-04 22:11:17 -08:00
parent cf1aa522aa
commit a5c76557d2
8 changed files with 222 additions and 131 deletions

View File

@ -1,41 +1,41 @@
board=klass() board=klass()
function board:init() function board:init(ruleset)
self.ruleset=ruleset
self.cursor=cursor:new(self) self.cursor=cursor:new(self)
self.slots={} self.slots={}
self.wells={} self.wells={}
-- board slots -- board slots
-- 1,11: normal -- ...n_slots: normal
for i=1,11 do for i=1,ruleset.n_slots do
add(self.slots,slot:new()) add(self.slots,slot:new(ruleset))
end end
-- 12: special -- n_slots+1: special
add(self.slots,slot:new(1)) add(self.slots,slot:new(ruleset,1))
assert(#self.slots==12)
-- wells: -- wells:
-- 1,2,3,4: wands, cups, swords, pentacles -- ...n_suits: wands, cups, swords, pentacles, etc
local function add_suit(_self,suit) local function add_suit(_self,suit)
add(_self.wells,well:new(function(lst,new) add(_self.wells,well:new(_self.ruleset, function(lst,new)
assert(lst) -- the ace is always present initially assert(lst) -- the ace is always present initially
if (new.suit!=suit) return if (new.suit!=suit) return
return new.suit==suit and new.rank==lst.rank+1 return new.suit==suit and new.rank==lst.rank+1
end)) end))
end end
for suit in all(deck.suits) do for suit in all(self.ruleset.deck.suits) do
add_suit(self,suit) add_suit(self,suit)
end end
-- 5: arcana ascending -- n_suits+1: arcana ascending
add(self.wells,well:new(function(lst,new) add(self.wells,well:new(self.ruleset,function(lst,new)
if (new.suit!='a') return if (new.suit!='a') return
if (not lst) return new.rank==0 if (not lst) return new.rank==0
return new.rank==lst.rank+1 return new.rank==lst.rank+1
end)) end))
-- 6: arcana descending -- n_suits+2: arcana descending
add(self.wells,well:new(function(lst,new) add(self.wells,well:new(self.ruleset,function(lst,new)
if (new.suit!='a') return if (new.suit!='a') return
if (not lst) return new.rank==21 if (not lst) return new.rank==self.ruleset.n_arcana-1
return new.rank==lst.rank-1 return new.rank==lst.rank-1
end)) end))
@ -43,24 +43,21 @@ function board:init()
end end
function board:deal() function board:deal()
local n_conventional_wells=4 local n_usable_slots=self.ruleset.n_slots - 1
local n_slots=10
-- first, pull the aces -- first, pull the aces
assert(#deck.aces==n_conventional_wells)
assert(#self.wells==n_conventional_wells+2)
local available={} local available={}
for card=1,#deck.cards do for card=1,#self.ruleset.deck.cards do
available[card]=true available[card]=true
end end
for i=1,n_conventional_wells do for i=1,#self.ruleset.deck.aces do
local well=self.wells[i] local well=self.wells[i]
local ace=deck.aces[i] local ace=self.ruleset.deck.aces[i]
well:add(ace) well:add(ace)
available[ace]=false available[ace]=false
end end
local eligible_bottom_row={} local eligible_bottom_row={}
for card=1,#deck.cards do for card=1,#self.ruleset.deck.cards do
local skip local skip
if not available[card] then if not available[card] then
skip=true skip=true
@ -73,20 +70,20 @@ function board:deal()
end end
function i_to_slot(i) function i_to_slot(i)
if (i<n_slots\2) return i+1 if (i<n_usable_slots\2) return i+1
return i+2 return i+2
end end
local bottom_row={} local bottom_row={}
shuf(eligible_bottom_row) shuf(eligible_bottom_row)
for i=1,n_slots do for i=1,n_usable_slots do
local card=eligible_bottom_row[i] local card=eligible_bottom_row[i]
add(bottom_row,card) add(bottom_row,card)
available[card]=false available[card]=false
end end
eligible={} eligible={}
for card=1,#deck.cards do for card=1,#self.ruleset.deck.cards do
if (available[card]) add(eligible,card) if (available[card]) add(eligible,card)
end end
shuf(eligible) shuf(eligible)
@ -94,7 +91,7 @@ function board:deal()
add(eligible,card) add(eligible,card)
end end
for i=1,#eligible do for i=1,#eligible do
local ix=i_to_slot((i-1)%n_slots) local ix=i_to_slot((i-1)%n_usable_slots)
local slot=self.slots[ix] local slot=self.slots[ix]
slot:add(eligible[i]) slot:add(eligible[i])
end end
@ -112,7 +109,7 @@ function board:_on_move_1()
local top=self.slots[s]:peek() local top=self.slots[s]:peek()
if top then if top then
for w=1,#self.wells do for w=1,#self.wells do
if w<=4 and self.slots[12]:peek()!=nil then if w<=self.ruleset.n_suits and self.slots[self.ruleset.n_slots+1]:peek()!=nil then
-- the top wells are blocked -- the top wells are blocked
elseif self.wells[w]:would_accept(top) then elseif self.wells[w]:would_accept(top) then
self.wells[w]:add(self.slots[s]:pop()) self.wells[w]:add(self.slots[s]:pop())
@ -126,11 +123,11 @@ end
function board:draw() function board:draw()
for w_ix=1,#self.wells do for w_ix=1,#self.wells do
local w=self.wells[w_ix] local w=self.wells[w_ix]
local l=layouts:well(w_ix) local l=self.ruleset.layouts:well(w_ix)
for i=1,#w.contents do for i=1,#w.contents do
local x,y=l:place_card(i) local x,y=l:place_card(i)
deck:draw_card(x,y,w.contents[i],i<#w.contents) self.ruleset.deck:draw_card(x,y,w.contents[i],i<#w.contents)
end end
end end
@ -138,7 +135,7 @@ function board:draw()
local function forall_slots(cb) local function forall_slots(cb)
for s_ix=1,#self.slots do for s_ix=1,#self.slots do
local s=self.slots[s_ix] local s=self.slots[s_ix]
local l=layouts:slot(s_ix) local l=self.ruleset.layouts:slot(s_ix)
local n=#s.contents local n=#s.contents
if (self.cursor.grabbed==s_ix) n-=1 if (self.cursor.grabbed==s_ix) n-=1
@ -150,7 +147,7 @@ function board:draw()
forall_slots(function(x,y,s_ix,s,l,n) forall_slots(function(x,y,s_ix,s,l,n)
for i=1,n do for i=1,n do
local x,y=l:place_card(i) local x,y=l:place_card(i)
deck:draw_card(x,y,s.contents[i],i<n) self.ruleset.deck:draw_card(x,y,s.contents[i],i<n)
end end
end) end)
@ -164,7 +161,8 @@ function board:draw()
end end
slot=klass() slot=klass()
function slot:init(max_n) function slot:init(ruleset,max_n)
self.ruleset=ruleset
self.contents={} self.contents={}
self.max_n=max_n self.max_n=max_n
end end
@ -174,8 +172,8 @@ function slot:would_accept(new)
local lst=self.contents[n] local lst=self.contents[n]
if (not lst) return true if (not lst) return true
lst=deck.cards[lst] lst=self.ruleset.deck.cards[lst]
new=deck.cards[new] new=self.ruleset.deck.cards[new]
return lst.suit==new.suit and (lst.rank==new.rank-1 or lst.rank==new.rank+1) return lst.suit==new.suit and (lst.rank==new.rank-1 or lst.rank==new.rank+1)
end end
function slot:add(card) function slot:add(card)
@ -189,15 +187,16 @@ function slot:pop()
end end
well=klass() well=klass()
function well:init(accept_cb) function well:init(ruleset,accept_cb)
self.ruleset=ruleset
self.accept_cb=accept_cb self.accept_cb=accept_cb
self.contents={} self.contents={}
end end
function well:would_accept(new) function well:would_accept(new)
local lst=self.contents[#self.contents] local lst=self.contents[#self.contents]
if (lst) lst=deck.cards[lst] if (lst) lst=self.ruleset.deck.cards[lst]
assert(new,"must be populated") assert(new,"must be populated")
new=deck.cards[new] new=self.ruleset.deck.cards[new]
assert(new,"must be populated") assert(new,"must be populated")
return self.accept_cb(lst,new) return self.accept_cb(lst,new)
end end

View File

@ -6,8 +6,9 @@ acceptance_state={
} }
cursor=klass() cursor=klass()
function cursor:init(board) function cursor:init(board)
self.ruleset=board.ruleset
self.board=board self.board=board
self.hover_x=0 self.hover_x=self.ruleset.n_slots\2
self.hover_y=1 self.hover_y=1
self.grabbed=nil self.grabbed=nil
end end
@ -50,7 +51,7 @@ end
function cursor:move_x(dx) function cursor:move_x(dx)
if (self.hover_y==0) return if (self.hover_y==0) return
self.hover_x+=dx self.hover_x+=dx
self.hover_x%=11 -- TODO: Don't hard-code self.hover_x%=self.ruleset.n_slots -- TODO: Don't hard-code
end end
function cursor:move_y(dy) function cursor:move_y(dy)
@ -59,7 +60,7 @@ function cursor:move_y(dy)
end end
function cursor:hover_slot() function cursor:hover_slot()
if (self.hover_y==0) return 12 if (self.hover_y==0) return self.ruleset.n_slots+1
return self.hover_x+1 return self.hover_x+1
end end
@ -81,7 +82,7 @@ function cursor:draw_at(x,y)
end end
if card then if card then
local card_fg=deck:draw_card(x,y,card,false) local card_fg=self.ruleset.deck:draw_card(x,y,card,false)
local fg=card_fg local fg=card_fg
if (acc==acceptance_state.would_accept) fg=9 if (acc==acceptance_state.would_accept) fg=9
if (fg) rect(x-1,y-1,x+9,y+16,fg) if (fg) rect(x-1,y-1,x+9,y+16,fg)

View File

@ -1,67 +0,0 @@
deck={
suits={"p","s","c","w"},
cards={},
rank_name="a23456789tjqk"
}
-- pentacles, swords, cups, wands
for suit in all(deck.suits) do
for rank=1,13 do
add(deck.cards,{suit=suit,rank=rank})
end
end
-- arcana
for rank=0,21 do
add(deck.cards,{suit="a",rank=rank})
end
assert(#deck.cards==74)
deck.aces={1,14,27,40}
for i in all(deck.aces) do
assert(deck.cards[i].rank==1)
end
function deck:draw_card(x,y,c,shadow)
local meta=deck.cards[c]
local s,fg
local bg,shadowbg
if meta.suit=='a' then
bg,shadowbg=1,1
if (shadow) bg,shadowbg=1,0
else
bg,shadowbg=7,7
--if (shadow) bg,shadowbg=7,6
end
rectfill(x,y,x+8,y+4,bg)
rectfill(x,y+5,x+8,y+15,shadowbg)
if (meta.suit=='p') s,fg=0,4
if (meta.suit=='s') s,fg=1,12
if (meta.suit=='c') s,fg=2,2
if (meta.suit=='w') s,fg=3,3
if (meta.suit=='a') fg=15
if meta.suit=='a' then
local rank=""..meta.rank
pal(7,15)
print(meta.rank,x+5-#rank*2,y+1,7)
spr(4,x,y+8)
pal()
else
local name=sub(deck.rank_name,meta.rank,meta.rank)
rectfill(x,y,x+3,y+6,fg)
print(name,x,y+1,7)
pal(7,fg)
spr(s,x+5,y+1)
if not shadow then
spr(15+meta.rank,x,y+8)
end
pal()
end
return fg
end

View File

@ -1,16 +1,3 @@
layouts={}
function layouts:well(i)
if (i<=4) return layout:new(69+i*10,1,layout_mode.obscured)
if (i==5) return layout:new(9,1,layout_mode.obscured)
if (i==6) return layout:new(19,1,layout_mode.obscured)
assert(false,"shouldn't be > 6")
end
function layouts:slot(i)
if (i<=11) return layout:new(9+(i-1)*10,18,layout_mode.vertical)
if (i==12) return layout:new(94,1,layout_mode.obscured)
assert(false,"shouldn't be > 12")
end
layout_mode={ layout_mode={
obscured=0, -- for wells obscured=0, -- for wells
vertical=1, -- for conventional slots vertical=1, -- for conventional slots
@ -27,5 +14,5 @@ end
function layout:place_card(i) function layout:place_card(i)
if (self.mode==layout_mode.obscured) return self.x,self.y if (self.mode==layout_mode.obscured) return self.x,self.y
if (self.mode==layout_mode.vertical) return self.x,self.y+(i-1)*6 if (self.mode==layout_mode.vertical) return self.x,self.y+(i-1)*6
assert(false,"unexpected mode") assert(false,"unexpected mode: "..self.mode)
end end

View File

@ -2,7 +2,7 @@ main={}
add(modules,main) add(modules,main)
function main:init() function main:init()
self.board=board:new() self.board=board:new(progression[1])
end end
function main:update() function main:update()

15
main.p8
View File

@ -4,16 +4,17 @@ __lua__
#include engine.lua #include engine.lua
#include board.lua #include board.lua
#include cursor.lua #include cursor.lua
#include deck.lua
#include layout.lua #include layout.lua
#include ruleset.lua
#include progression.lua
#include main.lua #include main.lua
__gfx__ __gfx__
00000000070000007770000000700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 00000000070000007770000000700000007000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
70700000070000007770000007000000070707000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 70700000070000007770000007000000770000000707070000000000000000000000000000000000000000000000000000000000000000000000000000000000
07000000070000007770000007000000007777000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 07000000070000007770000007000000070000000077770000000000000000000000000000000000000000000000000000000000000000000000000000000000
70700000777000000700000070000000077070000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 70700000777000000700000070000000070000000770700000000000000000000000000000000000000000000000000000000000000000000000000000000000
00000000070000007770000070000000007777700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 00000000070000007770000070000000077000000077777000000000000000000000000000000000000000000000000000000000000000000000000000000000
00000000000000000000000000000000070077700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 00000000000000000000000000000000000000000700777000000000000000000000000000000000000000000000000000000000000000000000000000000000
00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
00000000000000000000000000000000000000000000000000000000000070000000700000007000000000000000000000000000000000000000000000000000 00000000000000000000000000000000000000000000000000000000000070000000700000007000000000000000000000000000000000000000000000000000

16
progression.lua Normal file
View File

@ -0,0 +1,16 @@
progression={
-- level 1
ruleset:new(5,1,9,0),
-- level 2
-- ruleset:new(5,2,9,0),
-- level 3
-- ruleset:new(7,2,9,8),
-- level 4 (first challenging)
-- ruleset:new(9,3,9,16),
-- level 5
-- ruleset:new(9,3,11,18),
-- fortune's foundation
-- ruleset:new(11,4,13,22)
-- harder than fortune's foundation
-- ruleset:new(11,5,10,25)
}

154
ruleset.lua Normal file
View File

@ -0,0 +1,154 @@
ruleset=klass()
function ruleset:init(
-- Number of unlocked slots (up to 11)
-- Includes the one in the middle
-- For Fortune's Foundation: 11
n_slots,
-- Number of suits (up to 5)
-- This is also the number of RHS wells
-- For Fortune's Foundation: 4
n_suits,
-- Number of cards per suit (up to 13)
-- For Fortune's Foundation: 13
n_cards_per_suit,
-- Number of arcana (unbounded)
-- For Fortune's Foundation: 22
n_arcana
)
self.n_slots=n_slots
self.n_suits=n_suits
self.n_cards_per_suit=n_cards_per_suit
self.n_arcana=n_arcana
assert(self.n_slots<=11)
assert(self.n_suits<=5)
assert(self.n_cards_per_suit<=13)
assert(self.n_arcana<=99)
-- the middle slot is always auxiliary in
-- Fortune's Foundation-style games
local usable_slots = n_slots - 1
assert(usable_slots%2==0)
local total_n_cards = n_arcana + n_suits * n_cards_per_suit
-- aces aren't usable because they are initially placed in the wells
local usable_n_cards = total_n_cards - self.n_suits
-- deal has to be symmetrical
assert(usable_n_cards % usable_slots == 0, usable_slots-(usable_n_cards%usable_slots))
-- these cards would be instantly moved to the wells and
-- therefore cannot be in the bottom row
local instantly_placed_cards = self.n_suits + 2
assert((total_n_cards-instantly_placed_cards)-usable_slots >= 0)
self:generate_deck()
self:generate_layouts()
end
function ruleset:generate_deck()
local ruleset=self
local possible_suits={"p","s","c","w","b"}
local deck={
aces={},
suits={},
cards={},
rank_name="a23456789tjqk"
}
self.deck=deck
for i=1,self.n_suits do
add(deck.suits,possible_suits[i])
end
-- suited cards
for suit in all(deck.suits) do
for rank=1,self.n_cards_per_suit do
add(deck.cards,{suit=suit,rank=rank})
if (rank==1) add(deck.aces,#deck.cards)
end
end
-- arcana
for rank=0,self.n_arcana-1 do
add(deck.cards,{suit="a",rank=rank})
end
function deck:draw_card(x,y,c,shadow)
local meta=deck.cards[c]
local is_extreme=meta.rank==0 or meta.rank==ruleset.n_arcana-1
local s,fg
local bg,shadowbg
if meta.suit=='a' then
bg,shadowbg=1,1
if (shadow) bg,shadowbg=1,0
if (is_extreme) bg,shadowbg=8,8
else
bg,shadowbg=7,7
--if (shadow) bg,shadowbg=7,6
end
rectfill(x,y,x+8,y+4,bg)
rectfill(x,y+5,x+8,y+15,shadowbg)
if (meta.suit=='p') s,fg=0,4
if (meta.suit=='s') s,fg=1,12
if (meta.suit=='c') s,fg=2,2
if (meta.suit=='w') s,fg=3,3
if (meta.suit=='b') s,fg=3,0
if (meta.suit=='a') fg=15
if meta.suit=='a' then
local rank=""..meta.rank
pal(7,15)
if (is_extreme) pal(7,0)
print(meta.rank,x+5-#rank*2,y+1,7)
spr(5,x,y+8)
pal()
else
local name=sub(deck.rank_name,meta.rank,meta.rank)
rectfill(x,y,x+3,y+6,fg)
print(name,x,y+1,7)
pal(7,fg)
spr(s,x+5,y+1)
if not shadow then
spr(15+meta.rank,x,y+8)
end
pal()
end
return fg
end
end
function ruleset:generate_layouts()
local layouts={}
self.layouts=layouts
local ruleset=self
local width=ruleset.n_slots*10
local x=(128-width)\2
function layouts:well(i)
if i<=ruleset.n_suits then
local wx=width-ruleset.n_suits*10+(i-1)*10
return layout:new(x+wx,1,layout_mode.obscured)
end
i-=ruleset.n_suits
if (i==1) return layout:new(x,1,layout_mode.obscured)
if (i==2) return layout:new(x+10,1,layout_mode.obscured)
assert(false,"unknown well")
end
function layouts:slot(i)
if i<=ruleset.n_slots then
local sx=(i-1)*10
return layout:new(x+sx,18,layout_mode.vertical)
end
if (i==ruleset.n_slots+1) return layout:new(x+width-ruleset.n_suits*5-5,1,layout_mode.obscured)
assert(false, "unknown slot")
end
end