Add random deck option

This commit is contained in:
jar 2025-09-19 20:31:45 +00:00
parent 29192d156a
commit 35c0a4e4ae
8 changed files with 117 additions and 36 deletions

View file

@ -228,6 +228,7 @@ Matt Brubeck <mbrubeck@limpet.net>
Yaoliang Chen <yaoliang.ch@gmail.com> Yaoliang Chen <yaoliang.ch@gmail.com>
KolbyML <https://github.com/KolbyML> KolbyML <https://github.com/KolbyML>
Adnane Taghi <dev@soleuniverse.me> Adnane Taghi <dev@soleuniverse.me>
jariji
******************** ********************

@ -1 +1 @@
Subproject commit 0fe0162f4a18e8ef2fbac1d9a33af8e38cf7260e Subproject commit 480ef0da728c7ea3485c58529ae7ee02be3e5dba

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 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. 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 `Ascending position`: Gathers cards by ascending position (due #), which is typically
the oldest-added first. the oldest-added first.
@ -153,6 +155,7 @@ deck-config-new-gather-priority-tooltip-2 =
`Random cards`: Gathers cards in a random order. `Random cards`: Gathers cards in a random order.
deck-config-new-gather-priority-deck = Deck deck-config-new-gather-priority-deck = Deck
deck-config-new-gather-priority-deck-then-random-notes = Deck, then random notes deck-config-new-gather-priority-deck-then-random-notes = Deck, then random notes
deck-config-new-gather-priority-each-from-random-deck = Each from random deck
deck-config-new-gather-priority-position-lowest-first = Ascending position deck-config-new-gather-priority-position-lowest-first = Ascending position
deck-config-new-gather-priority-position-highest-first = Descending position deck-config-new-gather-priority-position-highest-first = Descending position
deck-config-new-gather-priority-random-notes = Random notes deck-config-new-gather-priority-random-notes = Random notes

@ -1 +1 @@
Subproject commit 17216b03db7249600542e388bd4ea124478400e5 Subproject commit fd5f984785ad07a0d3dbd893ee3d7e3671eaebd6

View file

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

View file

@ -1,6 +1,9 @@
// Copyright: Ankitects Pty Ltd and contributors // Copyright: Ankitects Pty Ltd and contributors
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html // License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
use rand::thread_rng;
use rand::Rng;
use std::collections::HashSet;
use super::DueCard; use super::DueCard;
use super::NewCard; use super::NewCard;
use super::QueueBuilder; use super::QueueBuilder;
@ -65,6 +68,10 @@ impl QueueBuilder {
NewCardGatherPriority::Deck => { NewCardGatherPriority::Deck => {
self.gather_new_cards_by_deck(col, NewCardSorting::LowestPosition) 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( NewCardGatherPriority::DeckThenRandomNotes => self.gather_new_cards_by_deck(
col, col,
NewCardSorting::RandomNotes(self.context.timing.days_elapsed), NewCardSorting::RandomNotes(self.context.timing.days_elapsed),
@ -112,6 +119,55 @@ impl QueueBuilder {
Ok(()) 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 = thread_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.gen_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.
return 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( fn gather_new_cards_sorted(
&mut self, &mut self,
col: &mut Collection, col: &mut Collection,

View file

@ -420,6 +420,21 @@ mod test {
]; ];
assert_eq!(col.queue_as_deck_and_template(parent.id), cards); 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(()) Ok(())
} }

View file

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