From bcfa629419ccd2c65973eea97de332a61daefa6a Mon Sep 17 00:00:00 2001 From: geemili Date: Sun, 18 Feb 2024 14:39:52 -0700 Subject: [PATCH] refactor: designed based on hypermedia principles --- rummy.lua | 648 +++++++++++++++++++++++++++--------------------------- 1 file changed, 329 insertions(+), 319 deletions(-) diff --git a/rummy.lua b/rummy.lua index a241a6f..cf91845 100644 --- a/rummy.lua +++ b/rummy.lua @@ -16,8 +16,9 @@ cards_in_meld_draft=nil melds_on_tabletop=nil -- `points_of_interest` is an array of things that can be hovered/selected by the controller +current_page=nil hovered=nil -player_state=nil +current_handler=nil function BOOT() draw_pile = create_deck() @@ -30,380 +31,376 @@ function BOOT() discard_pile = draw_pile:draw_stack(1) - player_state = player_state_draw_card + current_handler=handler_draw_card melds_on_tabletop={} end function TIC() - cls(12) + local c_sel=btnp(5) - local points_of_interest = player_state.get_points_of_interest() + local request=nil + if c_sel and hovered then + request=hovered.action + current_page=nil + end - local discard_x=((240 - 24) / 2) - local draw_pile_x=discard_x - 24 - 4 - local end_turn_x=discard_x + 24 + 4 + while not current_page do + trace(current_handler) + trace(request) + local response=current_handler(request) + if response.redirect then + current_handler=response.redirect + request=nil + elseif response.page then + current_page=response.page + else + cls(12) + print("Invalid response!") + for key,val in pairs(response) do + trace("key="..key) + trace("val="..val) + end + return nil + end + end - if not hovered and #points_of_interest>0 then - hovered=points_of_interest[1] + local actions = get_actions_from_page(current_page) + + if not hovered and #actions>0 then + hovered=actions[1] end local c_up=btnp(0,10,3) local c_down=btnp(1,10,3) local c_left=btnp(2,10,3) local c_right=btnp(3,10,3) - local c_sel=btnp(5) local c_back=btnp(4) - if c_sel and hovered then - player_state = player_state.update(hovered) - end - - -- update hovered position + -- update hovered action local moved = c_up or c_down or c_left or c_right - if moved and #points_of_interest > 0 then - local start = hovered or {x=0,y=0,interest=nil} - local dir = {0, 0} - if c_left then dir[1] = dir[1] - 1 end - if c_right then dir[1] = dir[1] + 1 end - if c_up then dir[2] = dir[2] - 1 end - if c_down then dir[2] = dir[2] + 1 end + if moved and #actions > 0 then + local start=hovered or {x=0,y=0,action=nil} + local dir={0, 0} + if c_left then dir[1]=dir[1] - 1 end + if c_right then dir[1]=dir[1] + 1 end + if c_up then dir[2]=dir[2] - 1 end + if c_down then dir[2]=dir[2] + 1 end - local nearest = nil - for i,point in ipairs(points_of_interest) do - if hovered and point.interest == hovered.interest then + local nearest=nil + for i,action in ipairs(actions) do + if hovered and action.action==hovered.action then -- pass elseif not nearest then - local distance = distance_to_point_of_interest(start,dir,point) - if distance > 0 then - nearest = point + local distance=distance_to_point_of_interest(start,dir,action) + if distance>0 then + nearest=action end else - local distance = distance_to_point_of_interest(start,dir,point) - if distance > 0 and distance < distance_to_point_of_interest(start,dir,nearest) then - nearest = point + local distance=distance_to_point_of_interest(start,dir,action) + if distance>0 and distance0 then + local meld_with_draft=meld:with(draft) + if rummy_is_valid_meld(meld_with_draft) then + meld_action=meld + end end for i,card in ipairs(meld) do - card:render(meld_x, 10, false, sel_state) + table.insert(elements, { + visual=card, + x=meld_x, y=meld_y, + action=meld_action, + }) meld_x=meld_x + 8 end meld_x=meld_x + 28 end - if is_an_interest(points_of_interest, "New\nMeld") then - print("New\nMeld", meld_x, 17, 0) - if hovered and hovered.interest=="New\nMeld" then - spr(Card.spr_hilight.sid, - meld_x, - 10, - Card.spr_hilight.colorkey, - 1, 0, 0, - Card.spr_hilight.tw, - Card.spr_hilight.th) + table.insert(elements, { + visual="New\nMeld", + textx=meld_x, texty=meld_y+7, + x=meld_x, y=meld_y, + action=((new_meld_allowed and rummy_is_valid_meld(draft)) and "New\nMeld" or nil), + action_x=meld_x, action_y=meld_y, + }) +end + +function build_draw_discard_ui(elements, options) + local discard_x=((240 - 24) / 2) + local discard_y=80 + local draw_pile_x=discard_x - 24 - 4 + local end_turn_x=discard_x + 24 + 4 + + -- render draw pile + for i,card in ipairs(draw_pile) do + if i==#draw_pile and options.is_drawing then + table.insert(elements, { + visual=card, + x=draw_pile_x, y=discard_y + (i - 1) * -0.25, + hidden=true, + action=card, + }) + else + table.insert(elements, { + visual=card, + x=draw_pile_x, y=discard_y + (i - 1) * -0.25, + hidden=true, + }) end - else - print("New\nMeld", meld_x, 17, 13) end -- render discard pile for i,card in ipairs(discard_pile) do - local sel_state = nil - if hovered and hovered.interest == card then - sel_state = 1 - end - - card:render(discard_x, 80 + (i - 1) * -0.25, false, sel_state) - end - if is_an_interest(points_of_interest, "Discard") then - if hovered and hovered.interest=="Discard" then - spr(Card.spr_hilight.sid, - discard_x, - 80 + (#discard_pile - 1) * -0.25, - Card.spr_hilight.colorkey, - 1, 0, 0, - Card.spr_hilight.tw, - Card.spr_hilight.th) + if i==#discard_pile and options.is_drawing then + table.insert(elements, { + visual=card, + x=discard_x, y=discard_y + (i - 1) * -0.25, + action=card, + }) + else + table.insert(elements, { + visual=card, + x=discard_x, y=discard_y + (i - 1) * -0.25 + }) end end + table.insert(elements, { + visual=(#discard_pile==0 and Card.sprite), + x=discard_x, y=discard_y + (#discard_pile - 1) * -0.25, + action=options.discard_action, + }) - -- render draw pile - for i,card in ipairs(draw_pile) do - local sel_state = nil - if hovered and hovered.interest == card then - sel_state = 1 - end + table.insert(elements, { + visual="End\nTurn", + textx=end_turn_x, texty=discard_y + 7, + x=end_turn_x, y=discard_y, + action=options.end_turn_action, + }) +end - card:render(draw_pile_x, 80 + (i - 1) * -0.25, true, sel_state) - end - - if is_an_interest(points_of_interest, "End\nTurn") then - print("End\nTurn", end_turn_x, 80+7, 0) - if hovered and hovered.interest=="End\nTurn" then - spr(Card.spr_hilight.sid, - end_turn_x, 80, - Card.spr_hilight.colorkey, - 1, 0, 0, - Card.spr_hilight.tw, - Card.spr_hilight.th) - end - else - print("End\nTurn", end_turn_x, 80+7, 13) - end +function build_hand_ui(elements, hand, draft, can_select) local hand_start_x=((240 - #cards_in_hand * 12 - 24) / 2) - for i,card in ipairs(cards_in_hand) do - local ty=0 - local sel_state = nil + local hand_y=110 + for i,card in ipairs(hand) do + if can_select then + local ty=0 + local sel_state = nil - if cards_in_meld_draft:contains(card) then - ty = -4 - sel_state = 2 + if draft:contains(card) then + ty = -4 + sel_state = 2 + end + + table.insert(elements, { + visual=card, + x=hand_start_x + (i - 1) * 12, y=hand_y + ty, + sel_state=sel_state, + action=card, action_y=hand_y, + }) + else + table.insert(elements, { + visual=card, + x=hand_start_x + (i - 1) * 12, y=hand_y + }) end - - if hovered and hovered.interest == card then - sel_state = 1 - end - - card:render(hand_start_x + (i - 1) * 12, 110 + ty, false, sel_state) end end -player_state_draw_card = { - update=function(point_of_interest) - -- only accept drawing cards from the draw pile or - if draw_pile:contains(point_of_interest.interest) then - table.insert(cards_in_hand, draw_pile:draw()) - cards_in_hand:sort(rummy_hand_sort_comparison) - hovered = nil - return player_state_action - elseif discard_pile:contains(point_of_interest.interest) then - table.insert(cards_in_hand, discard_pile:draw()) - cards_in_hand:sort(rummy_hand_sort_comparison) - hovered = nil - return player_state_action - end - return player_state_draw_card - end, - get_points_of_interest=function() - local points_of_interest = {} - - if #draw_pile > 0 then - table.insert(points_of_interest, { - x=3 + 12, - y=80 + (#draw_pile - 1) * -0.25 + 12, - interest=draw_pile[#draw_pile] - }) - end - if #discard_pile > 0 then - table.insert(points_of_interest, { - x=39 + 12, - y=80 + (#discard_pile - 1) * -0.25 + 12, - interest=discard_pile[#discard_pile] - }) - end - - return points_of_interest +function handler_draw_card(action) + if draw_pile:contains(action) then + table.insert(cards_in_hand, draw_pile:draw()) + cards_in_hand:sort(rummy_hand_sort_comparison) + hovered = nil + return { redirect=handler_player_action } + elseif discard_pile:contains(action) then + table.insert(cards_in_hand, discard_pile:draw()) + cards_in_hand:sort(rummy_hand_sort_comparison) + hovered = nil + return { redirect=handler_player_action } end -} -player_state_action = { - update=function(point_of_interest) - if cards_in_hand:contains(point_of_interest.interest) then - if cards_in_meld_draft:contains(point_of_interest.interest) then - table.remove(cards_in_meld_draft, cards_in_meld_draft:index_of(point_of_interest.interest)) - else - table.insert(cards_in_meld_draft, point_of_interest.interest) - cards_in_meld_draft:sort(rummy_hand_sort_comparison) - end - elseif point_of_interest.interest=="New\nMeld" then - if rummy_is_valid_meld(cards_in_meld_draft) then - for i,card in ipairs(cards_in_meld_draft) do - table.remove(cards_in_hand, cards_in_hand:index_of(card)) - end - table.insert(melds_on_tabletop, cards_in_meld_draft) - cards_in_meld_draft=CardStack:new() - hovered = nil - return player_state_secondary_action - end - elseif point_of_interest.interest=="Discard" then - if #cards_in_meld_draft==1 then - table.remove(cards_in_hand, cards_in_hand:index_of(cards_in_meld_draft[1])) - table.insert(discard_pile, cards_in_meld_draft[1]) - cards_in_meld_draft=CardStack:new() - hovered = nil - return player_state_discard_confirm - end - elseif table_index_of(melds_on_tabletop, point_of_interest.interest) then - local meld=point_of_interest.interest - local meld_with_draft=meld:with(cards_in_meld_draft) - if rummy_is_valid_meld(meld_with_draft) then - for i,card in ipairs(cards_in_meld_draft) do - table.remove(cards_in_hand, cards_in_hand:index_of(card)) - table.insert(meld, card) - end - meld:sort(rummy_hand_sort_comparison) - cards_in_meld_draft=CardStack:new() - end + local elements={} + + build_meld_ui(elements, cards_in_meld_draft, false) + build_draw_discard_ui(elements, { is_drawing=true }) + build_hand_ui(elements, cards_in_hand, cards_in_meld_draft, false) + + return { page=elements } +end + +function handler_player_action(action) + if cards_in_hand:contains(action) then + if cards_in_meld_draft:contains(action) then + table.remove(cards_in_meld_draft, cards_in_meld_draft:index_of(action)) + else + table.insert(cards_in_meld_draft, action) + cards_in_meld_draft:sort(rummy_hand_sort_comparison) end - return player_state_action - end, - get_points_of_interest=function() - local points_of_interest = {} - - for i,card in ipairs(cards_in_hand) do - table.insert(points_of_interest, { - x=(i - 1) * 12 + 2 + 5, - y=122, - interest=card - }) - end - + elseif action=="New\nMeld" then if rummy_is_valid_meld(cards_in_meld_draft) then - table.insert(points_of_interest, { - x=80 + 12, - y=17 + 6, - interest="New\nMeld" - }) - end - - if #cards_in_meld_draft==1 then - table.insert(points_of_interest, { - x=39, - y=80 + (#discard_pile - 1) * -0.25, - interest="Discard" - }) - end - - if #cards_in_meld_draft>0 then - for i,meld in ipairs(melds_on_tabletop) do - local meld_with_draft=meld:with(cards_in_meld_draft) - if rummy_is_valid_meld(meld_with_draft) then - table.insert(points_of_interest, { - x=i * 36, - y=10, - interest=meld - }) - end + for i,card in ipairs(cards_in_meld_draft) do + table.remove(cards_in_hand, cards_in_hand:index_of(card)) end - end - - return points_of_interest - end -} - --- In this state the player may only lay off a card or end their turn -player_state_secondary_action = { - update=function(point_of_interest) - if cards_in_hand:contains(point_of_interest.interest) then - if cards_in_meld_draft:contains(point_of_interest.interest) then - table.remove(cards_in_meld_draft, cards_in_meld_draft:index_of(point_of_interest.interest)) - else - table.insert(cards_in_meld_draft, point_of_interest.interest) - cards_in_meld_draft:sort(rummy_hand_sort_comparison) - end - elseif point_of_interest.interest=="Discard" then - if #cards_in_meld_draft==1 then - table.remove(cards_in_hand, cards_in_hand:index_of(cards_in_meld_draft[1])) - table.insert(discard_pile, cards_in_meld_draft[1]) - cards_in_meld_draft=CardStack:new() - hovered = nil - return player_state_discard_confirm - end - elseif table_index_of(melds_on_tabletop, point_of_interest.interest) then - local meld=point_of_interest.interest - local meld_with_draft=meld:with(cards_in_meld_draft) - if rummy_is_valid_meld(meld_with_draft) then - for i,card in ipairs(cards_in_meld_draft) do - table.remove(cards_in_hand, cards_in_hand:index_of(card)) - table.insert(meld, card) - end - meld:sort(rummy_hand_sort_comparison) - cards_in_meld_draft=CardStack:new() - end - end - return player_state_secondary_action - end, - get_points_of_interest=function() - local points_of_interest = {} - - for i,card in ipairs(cards_in_hand) do - table.insert(points_of_interest, { - x=(i - 1) * 12 + 2 + 5, - y=122, - interest=card - }) - end - - if #cards_in_meld_draft==1 then - table.insert(points_of_interest, { - x=39, - y=80 + (#discard_pile - 1) * -0.25, - interest="Discard" - }) - end - - if #cards_in_meld_draft>0 then - for i,meld in ipairs(melds_on_tabletop) do - local meld_with_draft=meld:with(cards_in_meld_draft) - if rummy_is_valid_meld(meld_with_draft) then - table.insert(points_of_interest, { - x=i * 36, - y=10, - interest=meld - }) - end - end - end - - return points_of_interest - end -} - -player_state_discard_confirm = { - update=function(point_of_interest) - if point_of_interest.interest=="End\nTurn" then + table.insert(melds_on_tabletop, cards_in_meld_draft) + cards_in_meld_draft=CardStack:new() hovered = nil - return player_state_draw_card + return { redirect=handler_player_secondary_action } end - - return player_state_discard_confirm - end, - get_points_of_interest=function() - local points_of_interest = {} - - table.insert(points_of_interest, { - x=80 + 12, - y=87 + 6, - interest="End\nTurn" - }) - - return points_of_interest - end -} - -function is_an_interest(points_of_interest,interest_sought) - for i,point in ipairs(points_of_interest) do - if point.interest==interest_sought then - return true + elseif action=="Discard" then + if #cards_in_meld_draft==1 then + table.remove(cards_in_hand, cards_in_hand:index_of(cards_in_meld_draft[1])) + table.insert(discard_pile, cards_in_meld_draft[1]) + cards_in_meld_draft=CardStack:new() + hovered = nil + return { redirect=handler_discard_confirm } + end + elseif table_index_of(melds_on_tabletop, action) then + local meld=point_of_interest.interest + local meld_with_draft=meld:with(cards_in_meld_draft) + if rummy_is_valid_meld(meld_with_draft) then + for i,card in ipairs(cards_in_meld_draft) do + table.remove(cards_in_hand, cards_in_hand:index_of(card)) + table.insert(meld, card) + end + meld:sort(rummy_hand_sort_comparison) + cards_in_meld_draft=CardStack:new() end end - return false + + local elements={} + + build_meld_ui(elements, cards_in_meld_draft, true) + build_draw_discard_ui(elements, { + discard_action=(#cards_in_meld_draft==1 and "Discard" or nil), + }) + build_hand_ui(elements, cards_in_hand, cards_in_meld_draft, true) + + return { page=elements } +end + +function handler_player_secondary_action(action) + if cards_in_hand:contains(action) then + if cards_in_meld_draft:contains(action) then + table.remove(cards_in_meld_draft, cards_in_meld_draft:index_of(action)) + else + table.insert(cards_in_meld_draft, action) + cards_in_meld_draft:sort(rummy_hand_sort_comparison) + end + elseif action=="Discard" then + if #cards_in_meld_draft==1 then + table.remove(cards_in_hand, cards_in_hand:index_of(cards_in_meld_draft[1])) + table.insert(discard_pile, cards_in_meld_draft[1]) + cards_in_meld_draft=CardStack:new() + hovered = nil + return { redirect=handler_discard_confirm } + end + elseif table_index_of(melds_on_tabletop, action) then + local meld=point_of_interest.interest + local meld_with_draft=meld:with(cards_in_meld_draft) + if rummy_is_valid_meld(meld_with_draft) then + for i,card in ipairs(cards_in_meld_draft) do + table.remove(cards_in_hand, cards_in_hand:index_of(card)) + table.insert(meld, card) + end + meld:sort(rummy_hand_sort_comparison) + cards_in_meld_draft=CardStack:new() + end + end + + local elements={} + + build_meld_ui(elements, cards_in_meld_draft, false) + build_draw_discard_ui(elements, { + discard_action=(#cards_in_meld_draft==1 and "Discard" or nil), + }) + build_hand_ui(elements, cards_in_hand, cards_in_meld_draft, true) + + return { page=elements } +end + +function handler_discard_confirm(action) + if action=="End\nTurn" then + hovered = nil + return { redirect=handler_draw_card } + end + + local elements={} + + build_meld_ui(elements, cards_in_meld_draft, false) + build_draw_discard_ui(elements, { + end_turn_action="End\nTurn", + }) + build_hand_ui(elements, cards_in_hand, cards_in_meld_draft, false) + + return { page=elements } end function distance_to_point_of_interest(start,dir,point_of_interest) @@ -559,46 +556,59 @@ function CardStack:with(other) end +Sprite={} +Sprite.__index=Sprite +function Sprite:new(obj) + local o = obj or {} + setmetatable(o, Sprite) + return o +end + -- Card rendering stuff suit_icon={34,35,33,36} suit_color={0,0,2,2} suit_names={'clubs','spades','hearts','diamonds'} rank_symbols={'A','2','3','4','5','6','7','8','9','10','J','Q','K'} Card = { - sprite={ + sprite=Sprite:new({ sid=5, tw=3, th=3, colorkey=14 - }, - spr_hilight={ + }), + spr_hilight=Sprite:new({ sid=8, tw=3, th=3, colorkey=14 - }, - spr_selected={ + }), + spr_selected=Sprite:new({ sid=11, tw=3, th=3, colorkey=14 - }, - spr_hidden={ + }), + spr_hidden=Sprite:new({ sid=56, tw=3, th=3, colorkey=14 - }, + }), } +Card.__index = Card function Card:new(suit, rank) local card = { suit=suit, rank=rank, } - setmetatable(card, { __index=Card }) + setmetatable(card, Card) return card end +function Card:__tostring(suit, rank) + return "<"..rank_symbols[self.rank].." of "..suit_names[self.suit]..">" +end + function Card:render(x, y, hidden, sel_state) if hidden then spr(self.spr_hidden.sid,