Add "each from random deck" gather priority

This commit is contained in:
jar 2025-09-19 23:31:21 +00:00
parent 3890e12c9e
commit f2923b92e1
6 changed files with 88 additions and 0 deletions

View file

@ -243,6 +243,7 @@ Lee Doughty <32392044+leedoughty@users.noreply.github.com>
memchr <memchr@proton.me>
Max Romanowski <maxr777@proton.me>
Aldlss <ayaldlss@gmail.com>
jariji
********************

View file

@ -142,6 +142,8 @@ deck-config-new-gather-priority-tooltip-2 =
can stop before all subdecks have been checked. This order is fastest in large collections, and
allows you to prioritize subdecks that are closer to the top.
`Each from random deck`: Repeatedly picks a random deck and takes the first card from it.
`Ascending position`: Gathers cards by ascending position (due #), which is typically
the oldest-added first.
@ -151,6 +153,7 @@ deck-config-new-gather-priority-tooltip-2 =
`Random notes`: Picks notes at random, then gathers all of its cards.
`Random cards`: Gathers cards in a random order.
deck-config-new-gather-priority-each-from-random-deck = Each from random deck
deck-config-new-card-sort-order = New card sort order
deck-config-new-card-sort-order-tooltip-2 =
`Card type, then order gathered`: Shows cards in order of card type number.

View file

@ -79,6 +79,8 @@ message DeckConfig {
NEW_CARD_GATHER_PRIORITY_RANDOM_NOTES = 3;
// Siblings are neither grouped nor ordered.
NEW_CARD_GATHER_PRIORITY_RANDOM_CARDS = 4;
// Pick one card from random deck.
NEW_CARD_GATHER_PRIORITY_EACH_FROM_RANDOM_DECK = 6;
}
enum NewCardSortOrder {
// Ascending card template ordinal.

View file

@ -1,6 +1,11 @@
// Copyright: Ankitects Pty Ltd and contributors
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
use std::collections::HashSet;
use rand::rng;
use rand::Rng;
use super::DueCard;
use super::NewCard;
use super::QueueBuilder;
@ -66,6 +71,9 @@ impl QueueBuilder {
NewCardGatherPriority::Deck => {
self.gather_new_cards_by_deck(col, NewCardSorting::LowestPosition)
}
NewCardGatherPriority::EachFromRandomDeck => {
self.gather_new_cards_by_each_from_random_deck(col, NewCardSorting::LowestPosition)
}
NewCardGatherPriority::DeckThenRandomNotes => {
self.gather_new_cards_by_deck(col, NewCardSorting::RandomNotes(salt))
}
@ -110,6 +118,53 @@ impl QueueBuilder {
Ok(())
}
fn gather_new_cards_by_each_from_random_deck(
&mut self,
col: &mut Collection,
sort: NewCardSorting,
) -> Result<()> {
let mut deck_ids = col.storage.get_active_deck_ids_sorted()?;
let mut rng = rng();
let mut cards_added = HashSet::<CardId>::new();
// Continue until global limit is reached or no more decks with cards.
while !self.limits.root_limit_reached(LimitKind::New) && !deck_ids.is_empty() {
let selected_index = rng.random_range(0..deck_ids.len());
let selected_deck = deck_ids[selected_index];
if self.limits.limit_reached(selected_deck, LimitKind::New)? {
// Remove the deck from the list since it's at its limit.
deck_ids.swap_remove(selected_index);
continue;
}
let mut found_card = false;
col.storage
.for_each_new_card_in_deck(selected_deck, sort, |card| {
let limit_reached = self.limits.limit_reached(selected_deck, LimitKind::New)?;
if limit_reached {
Ok(false)
} else if !cards_added.contains(&card.id) && self.add_new_card(card) {
cards_added.insert(card.id);
self.limits
.decrement_deck_and_parent_limits(selected_deck, LimitKind::New)?;
found_card = true;
// Stop iterating this deck after getting one card.
Ok(false)
} else {
Ok(true)
}
})?;
// If we couldn't find any card from this deck, remove it from consideration
if !found_card {
deck_ids.swap_remove(selected_index);
}
}
Ok(())
}
fn gather_new_cards_sorted(
&mut self,
col: &mut Collection,

View file

@ -425,6 +425,29 @@ mod test {
];
assert_eq!(col.queue_as_deck_and_template(parent.id), cards);
// each from random deck - test that we get expected count with variety across
// decks
col.set_deck_gather_order(&mut parent, NewCardGatherPriority::EachFromRandomDeck);
let cards = col.queue_as_deck_and_template(parent.id);
// Verify we get cards from multiple decks
let unique_decks: std::collections::HashSet<_> =
cards.iter().map(|(deck_id, _)| *deck_id).collect();
assert!(
unique_decks.len() > 1,
"EachFromRandomDeck should select from multiple decks"
);
// Verify the child limit is respected (child + grandchild <= 3)
let child_family_count = cards
.iter()
.filter(|(deck_id, _)| *deck_id == child.id || *deck_id == grandchild.id)
.count();
assert!(child_family_count <= 3, "Child limit should be respected");
// Should get 7 (out of 8) cards total (respects child limit of 3)
assert_eq!(cards.len(), 7);
Ok(())
}

View file

@ -25,6 +25,10 @@ export function newGatherPriorityChoices(): Choice<DeckConfig_Config_NewCardGath
label: tr.deckConfigNewGatherPriorityDeckThenRandomNotes(),
value: DeckConfig_Config_NewCardGatherPriority.DECK_THEN_RANDOM_NOTES,
},
{
label: tr.deckConfigNewGatherPriorityEachFromRandomDeck(),
value: DeckConfig_Config_NewCardGatherPriority.EACH_FROM_RANDOM_DECK,
},
{
label: tr.deckConfigNewGatherPriorityPositionLowestFirst(),
value: DeckConfig_Config_NewCardGatherPriority.LOWEST_POSITION,