Initial code

This commit is contained in:
Pyrex 2024-02-04 20:55:56 -08:00
commit cf1aa522aa
7 changed files with 520 additions and 0 deletions

206
board.lua Normal file
View File

@ -0,0 +1,206 @@
board=klass()
function board:init()
self.cursor=cursor:new(self)
self.slots={}
self.wells={}
-- board slots
-- 1,11: normal
for i=1,11 do
add(self.slots,slot:new())
end
-- 12: special
add(self.slots,slot:new(1))
assert(#self.slots==12)
-- wells:
-- 1,2,3,4: wands, cups, swords, pentacles
local function add_suit(_self,suit)
add(_self.wells,well:new(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))
end
for suit in all(deck.suits) do
add_suit(self,suit)
end
-- 5: arcana ascending
add(self.wells,well:new(function(lst,new)
if (new.suit!='a') return
if (not lst) return new.rank==0
return new.rank==lst.rank+1
end))
-- 6: arcana descending
add(self.wells,well:new(function(lst,new)
if (new.suit!='a') return
if (not lst) return new.rank==21
return new.rank==lst.rank-1
end))
self:deal()
end
function board:deal()
local n_conventional_wells=4
local n_slots=10
-- first, pull the aces
assert(#deck.aces==n_conventional_wells)
assert(#self.wells==n_conventional_wells+2)
local available={}
for card=1,#deck.cards do
available[card]=true
end
for i=1,n_conventional_wells do
local well=self.wells[i]
local ace=deck.aces[i]
well:add(ace)
available[ace]=false
end
local eligible_bottom_row={}
for card=1,#deck.cards do
local skip
if not available[card] then
skip=true
else
for w in all(self.wells) do
if (w:would_accept(card)) skip=true break
end
end
if (not skip) add(eligible_bottom_row,card)
end
function i_to_slot(i)
if (i<n_slots\2) return i+1
return i+2
end
local bottom_row={}
shuf(eligible_bottom_row)
for i=1,n_slots do
local card=eligible_bottom_row[i]
add(bottom_row,card)
available[card]=false
end
eligible={}
for card=1,#deck.cards do
if (available[card]) add(eligible,card)
end
shuf(eligible)
for card in all(bottom_row) do
add(eligible,card)
end
for i=1,#eligible do
local ix=i_to_slot((i-1)%n_slots)
local slot=self.slots[ix]
slot:add(eligible[i])
end
end
function board:on_move()
-- TODO: Make checkpoint
while true do
if (not self:_on_move_1()) break
end
end
function board:_on_move_1()
for s=1,#self.slots do
local top=self.slots[s]:peek()
if top then
for w=1,#self.wells do
if w<=4 and self.slots[12]:peek()!=nil then
-- the top wells are blocked
elseif self.wells[w]:would_accept(top) then
self.wells[w]:add(self.slots[s]:pop())
return true
end
end
end
end
end
function board:draw()
for w_ix=1,#self.wells do
local w=self.wells[w_ix]
local l=layouts:well(w_ix)
for i=1,#w.contents do
local x,y=l:place_card(i)
deck:draw_card(x,y,w.contents[i],i<#w.contents)
end
end
local function forall_slots(cb)
for s_ix=1,#self.slots do
local s=self.slots[s_ix]
local l=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)
deck:draw_card(x,y,s.contents[i],i<n)
end
end)
local hover_slot=self.cursor:hover_slot()
forall_slots(function(x,y,s_ix,s,l,n)
if hover_slot==s_ix then
local x,y=l:place_card(n+1)
self.cursor:draw_at(x,y)
end
end)
end
slot=klass()
function slot:init(max_n)
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=deck.cards[lst]
new=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(accept_cb)
self.accept_cb=accept_cb
self.contents={}
end
function well:would_accept(new)
local lst=self.contents[#self.contents]
if (lst) lst=deck.cards[lst]
assert(new,"must be populated")
new=deck.cards[new]
assert(new,"must be populated")
return self.accept_cb(lst,new)
end
function well:add(card)
add(self.contents,card)
end

91
cursor.lua Normal file
View File

@ -0,0 +1,91 @@
acceptance_state={
not_grabbed=0,
would_accept=1,
would_not_accept=2,
no_move=3
}
cursor=klass()
function cursor:init(board)
self.board=board
self.hover_x=0
self.hover_y=1
self.grabbed=nil
end
function cursor:acceptance_state()
if self.grabbed then
local hover=self:hover_slot()
if hover==self.grabbed then
return acceptance_state.no_move
end
local source=self.board.slots[self.grabbed]
local target=self.board.slots[self:hover_slot()]
local card=source:peek()
if target:would_accept(card) then
return acceptance_state.would_accept,source,target
else
return acceptance_state.would_not_accept
end
else
return acceptance_state.not_grabbed
end
end
function cursor:toggle_grab()
local acc,src,tar=self:acceptance_state()
if acc==acceptance_state.not_grabbed then
self.grabbed=self:hover_slot()
elseif acc==acceptance_state.would_accept then
local card=src:pop()
tar:add(card)
self.grabbed=nil
self.board:on_move(card)
elseif acc==acceptance_state.no_move or acc==acceptance_state.would_not_accept then
self.grabbed=nil
else
assert(false,"invalid acceptance state")
end
end
function cursor:move_x(dx)
if (self.hover_y==0) return
self.hover_x+=dx
self.hover_x%=11 -- TODO: Don't hard-code
end
function cursor:move_y(dy)
if (self.hover_y==0 and dy==1) self.hover_y=1
if (self.hover_y==1 and dy==-1) self.hover_y=0
end
function cursor:hover_slot()
if (self.hover_y==0) return 12
return self.hover_x+1
end
function cursor:grabbed_card()
if self.grabbed then
local slot=self.board.slots[self.grabbed]
return slot:peek()
end
return nil
end
function cursor:draw_at(x,y)
local card=self:grabbed_card()
local acc=self:acceptance_state()
if card and acc!=acceptance_state.would_accept and acc!=acceptance_state.no_move then
x+=sin(time()/2)*2+0.5
y+=sin(time()/4)+0.5+1
end
if card then
local card_fg=deck:draw_card(x,y,card,false)
local fg=card_fg
if (acc==acceptance_state.would_accept) fg=9
if (fg) rect(x-1,y-1,x+9,y+16,fg)
else
rectfill(x,y,x+8,y+15,9)
end
end

67
deck.lua Normal file
View File

@ -0,0 +1,67 @@
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

81
engine.lua Normal file
View File

@ -0,0 +1,81 @@
-- setup
modules={}
function _init()
-- printh("restarting")
_doall("init")
end
function _update60()
_doall("update") end
function _draw()
cls(0)
_doall("draw") end
function _doall(x)
for n in all{x.."0",x,x.."2",x.."3"} do
for mod in all(modules) do
local f=mod[n]
if (f) f(mod)
end
end
end
function klass()
local k={}
k.__index=k
function k:new(...)
local n={}
setmetatable(n,k)
n:init(...)
return n
end
return k
end
function alives(tbl)
local tbl2={}
for i in all(tbl) do
if (not i.dead) add(tbl2,i)
end
return tbl2
end
function trunc4(x)
if (x < 0) return -trunc4(-x)
return x\0.25/4
end
function stepstep(by,x0,x1,f)
local x=x0
if (x==x1) return
if x0>x1 then
return stepstep(by,-x0,-x1,function(x) return f(-x) end)
end
x=x\by*by
while true do
x+=by
if (x>=x1) f(x1) break
if (not f(x)) break
end
end
function lerp(x,x0,x1)
return x0+x*(x1-x0)
end
function csv(s)
local ret=split(s,"\n")
for i,v in ipairs(ret) do
ret[i] = type(v) == "string" and split(v) or {v} end
return ret
end
function shuf(t)
for i=#t,1,-1 do
local j=flr(rnd(i))+1
t[i],t[j]=t[j],t[i]
end
end

31
layout.lua Normal file
View File

@ -0,0 +1,31 @@
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={
obscured=0, -- for wells
vertical=1, -- for conventional slots
-- todo: sideways
}
layout=klass()
function layout:init(x,y,mode)
self.x=x
self.y=y
self.mode=mode
end
function layout:place_card(i)
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
assert(false,"unexpected mode")
end

19
main.lua Normal file
View File

@ -0,0 +1,19 @@
main={}
add(modules,main)
function main:init()
self.board=board:new()
end
function main:update()
if (btnp(0)) self.board.cursor:move_x(-1)
if (btnp(1)) self.board.cursor:move_x(1)
if (btnp(2)) self.board.cursor:move_y(-1)
if (btnp(3)) self.board.cursor:move_y(1)
if (btnp(4)) self.board.cursor:toggle_grab()
end
function main:draw()
cls(13)
self.board:draw()
end

25
main.p8 Normal file
View File

@ -0,0 +1,25 @@
pico-8 cartridge // http://www.pico-8.com
version 41
__lua__
#include engine.lua
#include board.lua
#include cursor.lua
#include deck.lua
#include layout.lua
#include main.lua
__gfx__
00000000070000007770000000700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
70700000070000007770000007000000070707000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
07000000070000007770000007000000007777000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
70700000777000000700000070000000077070000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
00000000070000007770000070000000007777700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
00000000000000000000000000000000070077700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
00000000000000000000000000000000000000000000000000000000000070000000700000007000000000000000000000000000000000000000000000000000
00000000000070000000700000700070007000700070007000700070007000700070007000700070070707000707070007070700000000000000000000000000
00000000000000000000000000000000000000000000000000000000000070000000700000007000007777000077770000777700000000000000000000000000
00007000000000000000700000000000000070000070007000707070000000000070007000700070077070000770700007707000000000000000000000000000
00000000000000000000000000000000000000000000000000000000000070000000000000007000007777700077777000777770000000000000000000000000
00000000000070000000700000700070007000700070007000700070007000700070007000700070070077700700777007007770000000000000000000000000
00000000000000000000000000000000000000000000000000000000000070000000700000007000000000000000000000000000000000000000000000000000