Generalized Quantum API#1351
Conversation
…more +Title, this is working with Shortcut and Four Fingers. -> Some edge cases have already come up (like straight_edge ranks), I've fixed the ones I've found but I'm certain there will be more. !Ironically the only parts that kept breaking stuff by the end were ""optimizations"" I thought of... maybe I should stop doing that woops. +Card:get_ranks() and Card:is_rank() functions -> These come with a new context type: context.get_ranks -> It only has the parameter "other_card" for the playing card that its called from. -> Returning "ranks" equal to a table of "SMODS.Rank"s will override the card's rank. !All of these features require SMODS.optional_features.quantum_ranks to be true !Documentation is lacking and there's still much to do, more commits to follow. Eventually. :)
…e optimizations *Title, straight_pos somehow resulted in card_reps being used multiple times which greatly hurt performance and didn't return the right cards. -> The new system seems to behave correctly and also removed any immediate performance problems with a crude wild rank implementation (-> simply returning all SMODS.Ranks in context.get_ranks) +A new exit condition for the straight_calculation based on the amount of card_reps checked or the total size of the best_straight so far. -> These should be edge case free (in theory) (hopefully) (please?) *context.get_ranks' "ranks" field now has the card's rank set as default.
…vanilla "get_id() == " and fixed some bugs +get_X_same() now works with quantum ranks too! -> This also means the previously injected "or_more" parameter is now part of the "overrides.lua" function definition and the according patches have been removed from "playing_cards.toml". +Added Card:is_any_rank() and SMODS.get_rank_by_id() functions. -> is_any_rank() allows passing a table of ranks, making Hack/Fibonacci/etc. easier to patch/implement. -> get_rank_by_id() does what its name implies. !I considered adding a lookup table alike SMODS.Ranks but by IDs, but for now I'll leave it. */+Most vanilla jokers now use Card:is_rank() instead of get_id() -> Exceptions are Even Steven, Odd Todd and Cloud Nine, due to implementation and potential performance problems. +Added some more comments to get_straight() -Removed semi functional implementation of 1-squashing optimization for get_straight(). -> I'll see if I'll get back to it, but it seems to run fine without.
+LSP defs for SMODS.get_rank_by_id() and get_ranks context field. *rank.straight_edge edge case handling had a mistake (probably) where the used_c_reps where being set incorrectly for the second straight evaluation.
*c_reps which have appeared in a ret_straight are skipped as starting points. -> As long as this doesn't cause any edge cases this may be a crucial optimization, because instead of checking every card_rep, it now only checks every branch of possible straights once. -> Please work :) (I beggeth, I pleadeth)
…ll oversight +´get_ranks´ context now has a ´source_context´ field, allowing more granular conditional rank changing (like, depending on what joker may have called is_rank(), though I'm unsure if that would also require a ´source_obj´ param) *Fixed missing ´pairs()´ in ´Card:is_any_rank()´
*Title, this required all my brainpower. ->The issue is now fixed and Full House can be correctly evaluated in the following scenario: 1. Jacks are also considered Kings and Queens. 2. The selected hand is K K Q Q J, or K Q J, or Q J J, or K Q Q J J, or K Q J J, evaluating as Full House, Pair, Three of a kind, Four of a kind and Three of a Kind respectively. ! The last hand is important; Technically there's also a Two Pair which my implementation does NOT pick up, but (barring any edgecases I'm unaware of), this will never be relevant because there will always be a Three of a kind (or better) instead. *Also fixed another missing `pairs()` in `utils.lua`
…into Quantum-Ranks
… during hand type evaluation + `Card:is_any_rank()` fix +Title. this default `source_context` allows you to selectively have cards only affect / not affect hand type evaluation. */+`Card:is_any_rank()` now works with rank ids or keys too.
…dicated `Full House` / `Two Pair` functions. */+Title, this was necessary because `Full House` and `Two Pair` didn't work. -> This means `get_X_same()` is now once again always called with `or_more=true`, and no longer accounts for quantum cards appearing multiple times, instead it returns all possible 'X (or >X) same's. -> `Full House` and `Two Pair` now call `get_quantum_full_house(_3, _2)` and `get_quantum_two_pair(_2)` respectively, these functions then check whether an unoverlapping `Pair` and `Three of a Kind` or a set of pairs exists. !This was way harder than straight evaluation lol. (I may have overcomplicated it here and there, this solution is super simple though. I hope it works.)
-Woopsie
…ded `Card:is_face()` compatibility (in `overrides.lua`) and added `no_mod` argument / flag to `context.get_ranks` *Title, those functions didn't need to be part of the `card_is_rank.toml` lovely patch. +Title, `Card:is_face()` now works with quantum ranks and uses a new source_context, `source_context.is_face_getting_ranks`, to allow exclusively returning certain ranks during / outside `is_face()` evaluation. +Title, `context.get_ranks` now takes a new return flag, `no_mod`, to ignore future returned `ranks`.
…ranks are toggled off. *Title
*Title, to avoid redundant context calculation.
-Title, this now allows overriding a card's ranks to be no rank at all.
*Title, it's now `Rank.straight_edges`, a map of ranks from which going into this rank will terminate a straight. -> I added this because I took a look at Paperback's Apostle implementation / definition and realised that its behavious wasn't currently supported that well. In theory it should now be possible to just not add the Apostle to the Ace's `straight_edges`, which will mean the straight calculation starting from the Apostle will pass through the Ace instead of terminating at it. ! I think the vanilla implementation (which still gets run if `SMODS.optional_features.quantum_ranks = false`) might need to be updated.
…`Rank.straight_edges` *Title, this means vanilla straight calc now also allows Paperback-Apostle-like straight rank shenanigans. (In theory)
*Title, also added `#best_straight >= #hand` to the `recursive_get_straight()` function.
…_return` +Title, this was an optimization I considered but had discarded due to potentially returning non-longest straights. -> If you want 20 card hands and quantum ranks you might have to toggle this on. !Check the PR for further info.
-Title, instead `is_rank()` etc. now take a `flags` param (used by eval for `eval_getting_ranks`) -> I hope the `SMODS.context_stack` PR gets merged, else I'll lose functionality here.
*Title
+/*Title, includes all injected QuantumCardField functions (e.g. `is_rank`, etc.)
-Title, whoopsie
*Title, this allows Jokers to have quantum Editions/Stickers too, and technically entails compatibility with quantum Enhancements/Seals etc. on Jokers, but that would require some more changes to function correctly.
|
Just wrote an overview over most (I think) stuff added in this PR, edited the top message! :) |
*Title
*Title
… hand. *Title, this includes adding a new function to `SMODS.CardAbilityField`, `check_context_criteria()`. -> It checks whether the context has the appropriate `cardarea` set and additionally, whether it has all the flags defined in `self.context_criteria`.
…tle more useful *Title, it now actually compares with the boolean value of the flag to be checked.
…ds_from_highlighted()` -/*Title
*Title
|
I did a brief performance test without any metrics; A Joker calling Edit: I've now done the above ^. FPS with one Joker doesn't seem to have changed (~70, down from ~115), but it only reaches ~60, down from ~115 with five Jokers, probably because there's no more Events. ! I could still use someone testing this on a lower-end device, lmk (here or in Discord) if you want to set up a test and need help with the API. |
*Title -Removed `SMODS.clear_quantum_cache()` as the whole cache now gets cleared as a hook into `Game:update()`.
…ard:set_seal()` + WIP quantum ability refactor/rework for compat +/*Title, lots untested here, and maybe not quite optimal either.
*Title, legacy Edition compat.
|
I've now made it so that |
+Title
For questions/suggestions, respond here or ping me on Discord!
SMODS.QuantumCardFieldA
QuantumCardField(QField) defines an aspect of a card that can be multi-valued / which can be dynamically modified. By default the following QFields are defined;rank,enhancement,seal,edition,stickerandsuit. Each has to be enabled by settingSMODS.optional_features.quantum_fields[key] = true, or for custom fields (e.g. for Paperback'spaperclips) by settingdefault_enabled = truein the object definition.All QFields inject the following functions (where
keyis the QField key, e.g.rank);Card:get_[key]s(args)This function gets all
[key]values of a card, e.g. allenhancements. This works using two contexts;card_has_checkand_quantum_getter, the latter of which also defines flags for every active QField, e.g.get_ranksfor ranks orcheck_enhancementfor enhancements.The
card_has_checkcontext is calculated to check whether a card counts asno_[key]orany_[key], which in turn defines the card's base[key]values.With the base values set in
SMODS.qfield_cache[card].get[key]for all QFields, and incontext.[key]sfor every active QField, the general_quantum_gettercontext is then calculated.Finally, the function returns the value map from
SMODS.qfield_cache[card].get[key].Card:set_[key](value, args, ...)(only forEditionandSeal+Enhancement, which redirects toCard:set_ability())This function unifies
Card:set_seal()andCard:set_edition(), and works just like those too (+ it is compatible with old function signatures). It also additionally callsSMODS.clear_quantum_cache(card).SMODS.has_no_[key](card, args)This function just calls the local function
_general_quantum_getter()to setSMODS.qfield_cache[card].has[key].no, which it then returns.SMODS.has_any_[key](card, args)This function just calls the local function
_general_quantum_getter()to setSMODS.qfield_cache[card].has[key].any, which it then returns.Card:is_[key](value, args)This function just converts
valueinto a map and calls its plural variant. (see below)Card:is_[key]s(values_map, args)This function gets the card's
[key]values and compares them with thevalues_map; Unlessargs.all = true, if any of the card's[key]values is in thevalues_map, returnstrue, else it checks all values and returnstrueif all match.SMODS.get_[key]_tally(cards, args)This function tallies all
[key]s acrosscards, e.g. it counts allenhancementsinG.hand.cards. It then returns atally = {[value 1] = [count], ...}map and avalue_to_cards = {[value 1 ] = [cards that had it], ...}map.Card:calculate_[key](context, args)This function gets all the card's
[key]values, converts them into objects using the required QField object fieldg_obj_table(e.g.SMODS.Seals-> justSMODS.Seal.obj_table), setsSMODS.qfield_cache[card].active_abilityto the corresponding ability and calls:calculate(context)on them if defined. The returned effects are then merged into one table usingSMODS.merge_effects().SMODS.CardAbilityFieldA
CardAbilityFielddefines a card value which is (typically) stored incard.ability, e.g.chip_multfor mult,chip_x_multfor x_mult etc., which is automatically retrieved and evaluated ineval_card(). This is done by iterating over allSMODS.CardAbilityFields and calling:insert_value()on every appropriate one (according toCardAbilityField.scoring_card_areas).Example
SMODS.CardAbilityFields;:insert_value(card, ret_table)This function first gets all
abilitys of a card by callingSMODS.get_card_abilities(card), and then callsself:getter(abilities, card). (see below)Then it inserts the returned sum into
ret_table.playing_card[self.calc_key] = [sum].:getter(abilities, card)This function iterates over
abilitiesand gets theCardAbilityFieldvalue usingtable_get_subfield(ability or {}, v_ref), wherev_refrefers to iterating overself.variant_refs, which is mostly equivalent to{self.value_ref}(onlyx_multdefinesvariant_refs = {"x_mult", "Xmult"}).The function then stacks values based on
self.stacking_type, adding if it equalsSMODS.CARD_VALUE_TYPES.ADDITIVE, or multiplying if it equalsSMODS.CARD_VALUE_TYPES.MULTIPLICATIVE.It also adds
permavalues according toself.perma_value_ref, which is evaluated only once on the card's actual.ability.:check_context_criteria(context)This function checks whether a context has the right
cardareaset (in accordance withCardAbilityField.scoring_card_areas, and, if any are defined, whether all flags inCardAbilityField.context_criteria(flag map) are set in the context.Quantum Rank Hand Calculation
(More in-depth details can be found in the closed Quantum Ranks PR #838)
If
SMODS.optional_features.quantum_fields.rank = trueis set, Hand Calc will use allrankvalues of a card. This may cause lag depending on the number of cards selected or in hand, mostly due to the newget_straight()implementation.Utils
SMODS.set_quantum_cache(card)This function just calls the local function
_general_quantum_getter(card)to setSMODS.qfield_cache[card].SMODS.clear_quantum_cache(card)This function resets a card's
ability/ its metatable and removescard._base_ability, and then clears the cache for the card (SMODS.qfield_cache[card] = nil). It is used byCard:set_ability()and the like.SMODS.get_card_abilities(card)This function calls
SMODS.set_quantum_cache(card)ifSMODS.qfield_cache[card].abilitiesis not defined, and then returnsSMODS.qfield_cache[card].abilities.SMODS.get_ability_from_obj(obj, card)This function creates and gets an ability table from a center
obj. It is used to cache QField values' abilities inSMODS.qfield_cache[card].abilities.Card:quantum_ability_set_mt()This function moves
card.abilityintocard._base_abilityand setscard.abilityto be an empty table, with a metatable which redirects acesses toSMODS.qfield_cache[card].active_ability(which is set before quantum calculating in e.g.Card:calculate_seal()).table_get_subfield(_table, key_string)This function first splits
key_stringinto multiple keys at every., setsret = _tableand then iteratively tries to setret = ret[key]until it fails and returnsnilor until it runs out of keys and returnsret. (e.g. passing a playing card as_tableandability.base.valueaskey_stringwould returncard.ability.base.value)Card:is_parity(parity)This function gets all of the card's
ranks to check if any of their.paritys equalsparity(0for even,1for odd).Card:is_royal()This function gets all of the card's
ranks to check if any of their.is_royals istrue.SMODS.all_royal(cards)This function calls
Card:is_royal()on each card incardsand returnstrueif they all are royal.SMODS.lowest_and_highest_rank(cards)This function gets all of the card's
ranks and sorts them by theirsort_nominal(how they are sorted in hand), and returns the first and last ranks.Additional Info: