Use functional loops over imperative

This commit is contained in:
Emik 2026-01-30 12:37:38 +01:00
parent 341a12bf65
commit e62eec9de5
Signed by: emik
GPG key ID: 6B0CD72A5E503BDF
9 changed files with 83 additions and 93 deletions

View file

@ -1 +1 @@
../../BalatroBakery/src/ ../../BalatroBakery/

View file

@ -1 +0,0 @@
../../Talisman/

View file

@ -1,4 +1,4 @@
local _, q = unpack(... or require "src.functional") local f, q = unpack(... or require "src.functional")
SMODS.Atlas { SMODS.Atlas {
px = 34, px = 34,
@ -16,26 +16,26 @@ local function common_rank()
--- Tallies up a card area's cards. --- Tallies up a card area's cards.
---@param card_area CardArea ---@param card_area CardArea
local function tally_up(card_area) local function tally_up(card_area)
for _, v in ipairs(card_area.cards) do f(card_area.cards):filter(function(v)
if not SMODS.has_no_rank(v) then return not SMODS.has_no_rank(v)
local id = v:get_id() end):foreach(function(v)
tally[id] = (tally[id] or 0) + 1 local id = v:get_id()
to_name[id] = v.base.value to_name[id] = v.base.value
end tally[id] = (tally[id] or 0) + 1
end end)
end end
tally_up(G.deck) tally_up(G.deck)
tally_up(G.hand) tally_up(G.hand)
tally_up(G.discard) tally_up(G.discard)
local max_key, max_value = -1 / 0, -1 / 0
for k, v in pairs(tally) do local max_key = f(tally):reduce({-1 / 0, -1 / 0}, function(a, v, k)
if v > max_value or k > max_key and v == max_value then if v > a[1] or k > a[2] and v == a[1] then
max_key = k return {v, k}
max_value = v
end end
end
return a
end)[2]
return max_key, to_name[max_key] return max_key, to_name[max_key]
end end
@ -115,6 +115,7 @@ SMODS.Blind {
pronouns = "she_her", pronouns = "she_her",
mult = 2, mult = 2,
dollars = 5, dollars = 5,
config = {draw = 5},
defeat = function(self) defeat = function(self)
self.disabled = false self.disabled = false
end, end,
@ -127,13 +128,9 @@ SMODS.Blind {
return false return false
end end
for i, v in ipairs(G.hand.cards) do f(G.hand.cards, ipairs):take(self.config.draw):foreach(function(v)
if i > 5 then
break
end
G.hand:add_to_highlighted(v, true) G.hand:add_to_highlighted(v, true)
end end)
G.FUNCS.play_cards_from_highlighted(nil) G.FUNCS.play_cards_from_highlighted(nil)
end end
@ -159,13 +156,15 @@ SMODS.Blind {
dollars = 5, dollars = 5,
disable = function() disable = function()
local function disable() local function disable()
for i = 1, #G.discard.cards do local count = #G.discard.cards
draw_card(G.discard, G.hand, i / #G.discard.cards * 100, "up", false, G.discard.cards[i], nil, nil, true)
end
for i = 1, #G.discard.cards do f(G.discard.cards):take(count):foreach(function(v, i)
draw_card(G.hand, G.deck, i / #G.discard.cards * 100, "up", false, G.discard.cards[i]) draw_card(G.discard, G.hand, i / count * 100, "up", false, v, nil, nil, true)
end end)
f(G.discard.cards):take(count):foreach(function(v, i)
draw_card(G.hand, G.deck, i / count * 100, "up", false, v)
end)
end end
q(disable) q(disable)
@ -225,11 +224,11 @@ SMODS.Blind {
local needs_text_change = false local needs_text_change = false
local function process(card_area) local function process(card_area)
for _, v in pairs(card_area.cards) do f(card_area.cards):foreach(function(v)
local debuff = v.debuff local debuff = v.debuff
v:set_debuff(self:recalc_debuff(v, false)) v:set_debuff(self:recalc_debuff(v, false))
needs_text_change = needs_text_change or debuff ~= v.debuff needs_text_change = needs_text_change or debuff ~= v.debuff
end end)
end end
process(G.deck) process(G.deck)
@ -265,16 +264,16 @@ SMODS.Blind {
end end
local cards_added = {} local cards_added = {}
local length = #G.hand.highlighted local count = #G.hand.highlighted
for i = 1, length do f(G.hand.highlighted, ipairs):take(count):foreach(function(v, i)
local copy = copy_card(G.hand.highlighted[i]) local copy = copy_card(G.hand.highlighted[i])
copy:add_to_deck() copy:add_to_deck()
table.insert(G.hand, copy) table.insert(G.hand, copy)
table.insert(cards_added, copy) table.insert(cards_added, copy)
table.insert(G.playing_cards, copy) table.insert(G.playing_cards, copy)
draw_card(G.hand, G.discard, i / length * 100, "down", false, copy, nil, nil, true) draw_card(G.hand, G.discard, i / count * 100, "down", false, copy, nil, nil, true)
end end)
playing_card_joker_effects(cards_added) playing_card_joker_effects(cards_added)
end, end,

View file

@ -23,7 +23,7 @@ local function protect_ev(fun)
return fun return fun
end end
local luaf = assert(SMODS.load_file("src/LuaFunctional/functional.lua"))() local luaf = assert(SMODS.load_file("src/LuaFunctional/functional.lua"))() or require "src.LuaFunctional.functional"
if false then if false then
---Returns the elements from the given `list`. This function is equivalent to ---Returns the elements from the given `list`. This function is equivalent to
@ -54,17 +54,18 @@ if false then
function unpack(list, i, j) function unpack(list, i, j)
error({list, i, j}) error({list, i, j})
end end
luaf = require "src.LuaFunctional.functional"
end end
---@class Query<K, V>: metatable
local _ = luaf._proto
--- Creates a non-numerically-indexed query from the given table. --- Creates a non-numerically-indexed query from the given table.
---@param obj table ---@param obj table
---@param f_pairs? fun(t: table): unknown ---@param f_pairs? fun(t: table): unknown
---@param numeric boolean? ---@param numeric boolean?
---@return Query ---@return Query
local function f(obj, f_pairs, numeric) local function f(obj, f_pairs, numeric)
return f_pairs and luaf.from(obj, f_pairs, numeric ~= false) or luaf.pairs(obj) return f_pairs and luaf.from(obj, f_pairs, numeric ~= false) or (obj[1] and luaf.ipairs(obj) or luaf.pairs(obj))
end end
--- Queues an event to be run. --- Queues an event to be run.
@ -75,7 +76,15 @@ local function q(fun, front)
G.E_MANAGER:add_event(protect_ev(fun), nil, front) G.E_MANAGER:add_event(protect_ev(fun), nil, front)
end end
---@class Query<K, V>: metatable --- Determines if a center is allowed to be usable.
local _ = luaf._proto ---@return boolean
local function u()
return not ((G.play and #G.play.cards > 0 or
G.CONTROLLER.locked or
(G.GAME.STOP_USE and G.GAME.STOP_USE > 0)) and
G.STATE ~= G.STATES.HAND_PLAYED and
G.STATE ~= G.STATES.DRAW_TO_HAND and
G.STATE ~= G.STATES.PLAY_TAROT)
end
return {f, q} return {f, q, u}

View file

@ -1,13 +1,4 @@
local f, q = unpack(... or require "src.functional") local f, q, u = unpack(... or require "src.functional")
local function can_use()
return not ((G.play and #G.play.cards > 0 or
G.CONTROLLER.locked or
(G.GAME.STOP_USE and G.GAME.STOP_USE > 0)) and
G.STATE ~= G.STATES.HAND_PLAYED and
G.STATE ~= G.STATES.DRAW_TO_HAND and
G.STATE ~= G.STATES.PLAY_TAROT)
end
local function common_hand() local function common_hand()
return (G.GAME or {}).current_round and f(G.GAME.hands):reduce( return (G.GAME or {}).current_round and f(G.GAME.hands):reduce(
@ -103,7 +94,7 @@ SMODS.Joker {
end, end,
---@param card Card ---@param card Card
Bakery_can_use = function(_, card) Bakery_can_use = function(_, card)
return not card.debuff and can_use() and ( return not card.debuff and u() and (
#G.GAME.tags ~= 0 or #G.GAME.tags ~= 0 or
f(G.consumeables.cards):filter(destructible):any() or f(G.consumeables.cards):filter(destructible):any() or
f(G.jokers.cards):filter(is_mergeable_with(card)):any() f(G.jokers.cards):filter(is_mergeable_with(card)):any()

View file

@ -18,3 +18,7 @@ SMODS.Atlas {
key = "modicon", key = "modicon",
path = "icon.png", path = "icon.png",
} }
SMODS.current_mod.optional_features = function()
return {cardareas = {deck = true}}
end

View file

@ -1,3 +1,5 @@
local f, _ = unpack(... or require "src.functional")
-- SMODS.Atlas { -- SMODS.Atlas {
-- px = 71, -- px = 71,
-- py = 95, -- py = 95,
@ -10,23 +12,26 @@ SMODS.Seal {
badge_colour = HEX("12f254"), badge_colour = HEX("12f254"),
atlas = "void", atlas = "void",
pos = {x = 0, y = 0}, pos = {x = 0, y = 0},
config = {ready = true},
calculate = function(_, card, context) calculate = function(_, card, context)
if not context.blind_defeated or card.area ~= G.deck then if context.setting_blind or context.starting_shop then
card.Roland_frost = nil
end
if not context.end_of_round or card.area ~= G.deck or card.Roland_frost then
return return
end end
card.Roland_frost = true
local tag = Tag(get_next_tag_key("Roland_frost")) local tag = Tag(get_next_tag_key("Roland_frost"))
if tag.name == "Orbital Tag" then if tag.name == "Orbital Tag" then
local hands = {} tag.ability.orbital_hand = pseudorandom_element(
f(G.GAME.hands):filter(function(v)
for k, v in pairs(G.GAME.hands) do return v.visible
if v.visible then end):into(),
table.insert(hands, k) pseudoseed("Roland_frost")
end )
end
tag.ability.orbital_hand = pseudorandom_element(hands, pseudoseed("Roland_frost"))
end end
add_tag(tag) add_tag(tag)

View file

@ -1,13 +1,4 @@
local f, q = unpack(... or require "src.functional") local f, q, u = unpack(... or require "src.functional")
local function can_use()
return not ((G.play and #G.play.cards > 0 or
G.CONTROLLER.locked or
(G.GAME.STOP_USE and G.GAME.STOP_USE > 0)) and
G.STATE ~= G.STATES.HAND_PLAYED and
G.STATE ~= G.STATES.DRAW_TO_HAND and
G.STATE ~= G.STATES.PLAY_TAROT)
end
SMODS.Sound({key = "void", path = "void.ogg"}) SMODS.Sound({key = "void", path = "void.ogg"})
@ -37,10 +28,10 @@ SMODS.Consumable {
return {vars = {card.ability.extra.amount, card.ability.extra.hand}} return {vars = {card.ability.extra.amount, card.ability.extra.hand}}
end, end,
can_use = function(_, card) can_use = function(_, card)
return can_use() and card.ability.extra.amount == #Bakery_API.get_highlighted() return u() and card.ability.extra.amount == #Bakery_API.get_highlighted()
end, end,
use = function(_, card, _, _) use = function(_, card, _, _)
for _, v in ipairs(Bakery_API.get_highlighted()) do f(Bakery_API.get_highlighted()):foreach(function(v)
q { q {
delay = 0.1, delay = 0.1,
func = function() func = function()
@ -48,7 +39,7 @@ SMODS.Consumable {
v:juice_up(0.5, 0.5) v:juice_up(0.5, 0.5)
end, end,
} }
end end)
q { q {
delay = 0.1, delay = 0.1,
@ -75,7 +66,7 @@ SMODS.Consumable {
return {vars = {card.ability.extra.amount}} return {vars = {card.ability.extra.amount}}
end, end,
can_use = function(_, _) can_use = function(_, _)
return #G.playing_cards > 1 and can_use() return #G.playing_cards > 1 and u()
end, end,
use = function(_, card, _, _) use = function(_, card, _, _)
local function destructible(v) local function destructible(v)
@ -102,17 +93,7 @@ SMODS.Consumable {
f(G.jokers.cards):foreach(calculate_joker) f(G.jokers.cards):foreach(calculate_joker)
for _ = 1, card.ability.extra.amount do for _ = 1, card.ability.extra.amount do
local cryptid = create_card( local cryptid = create_card(nil, G.consumeables, nil, nil, nil, nil, "c_cryptid", "void")
nil,
G.consumeables,
nil,
nil,
nil,
nil,
"c_cryptid",
"void"
)
cryptid:set_edition({negative = true}, true) cryptid:set_edition({negative = true}, true)
cryptid:add_to_deck() cryptid:add_to_deck()
G.consumeables:emplace(cryptid) G.consumeables:emplace(cryptid)

View file

@ -1,3 +1,5 @@
local f = unpack(... or require "src.functional")
if not Balatest then if not Balatest then
return return
end end
@ -139,13 +141,13 @@ Balatest.TestPlay {
Balatest.wait_for_input() Balatest.wait_for_input()
end, end,
assert = function() assert = function()
for k, v in pairs(G.hand.cards) do f(G.hand.cards):foreach(function(v, k)
local message = v.debuff and local message = v.debuff and
"Card " .. k .. " should not be debuffed" or "Card " .. k .. " should not be debuffed" or
"Card " .. k .. " should be debuffed" "Card " .. k .. " should be debuffed"
Balatest.assert(v.debuff == (v.base.value == "Ace"), message) Balatest.assert(v.debuff == (v.base.value == "Ace"), message)
end end)
end, end,
} }
@ -164,13 +166,13 @@ Balatest.TestPlay {
Balatest.play_hand {"2S"} Balatest.play_hand {"2S"}
end, end,
assert = function() assert = function()
for k, v in pairs(G.hand.cards) do f(G.hand.cards):foreach(function(v, k)
local message = v.debuff and local message = v.debuff and
"Card " .. k .. " should not be debuffed" or "Card " .. k .. " should not be debuffed" or
"Card " .. k .. " should be debuffed" "Card " .. k .. " should be debuffed"
Balatest.assert(v.debuff == (v.base.value == "2"), message) Balatest.assert(v.debuff == (v.base.value == "2"), message)
end end)
end, end,
} }
@ -183,9 +185,9 @@ Balatest.TestPlay {
Balatest.wait_for_input() Balatest.wait_for_input()
end, end,
assert = function() assert = function()
for k, v in pairs(G.hand.cards) do f(G.hand.cards):foreach(function(v, k)
Balatest.assert(not v.debuff, "Card " .. k .. " should not be debuffed") Balatest.assert(not v.debuff, "Card " .. k .. " should not be debuffed")
end end)
end, end,
} }