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

@ -187,7 +187,7 @@ Christian Donat <https://github.com/cdonat2>
Asuka Minato <https://asukaminato.eu.org> Asuka Minato <https://asukaminato.eu.org>
Dillon Baldwin <https://github.com/DillBal> Dillon Baldwin <https://github.com/DillBal>
Voczi <https://github.com/voczi> Voczi <https://github.com/voczi>
Ben Nguyen <105088397+bpnguyen107@users.noreply.github.com> Ben Nguyen <105088397+bpnguyen107@users.noreply.github.com>
Themis Demetriades <themis100@outlook.com> Themis Demetriades <themis100@outlook.com>
Luke Bartholomew <lukesbart@icloud.com> Luke Bartholomew <lukesbart@icloud.com>
Gregory Abrasaldo <degeemon@gmail.com> Gregory Abrasaldo <degeemon@gmail.com>
@ -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

@ -102,7 +102,7 @@ deck-config-leech-threshold-tooltip =
# See actions-suspend-card and scheduling-tag-only for the wording # See actions-suspend-card and scheduling-tag-only for the wording
deck-config-leech-action-tooltip = deck-config-leech-action-tooltip =
`Tag Only`: Add a 'leech' tag to the note, and display a pop-up. `Tag Only`: Add a 'leech' tag to the note, and display a pop-up.
`Suspend Card`: In addition to tagging the note, hide the card until it is `Suspend Card`: In addition to tagging the note, hide the card until it is
manually unsuspended. manually unsuspended.
@ -123,7 +123,7 @@ deck-config-bury-priority-tooltip =
When Anki gathers cards, it first gathers intraday learning cards, then When Anki gathers cards, it first gathers intraday learning cards, then
interday learning cards, then review cards, and finally new cards. This affects interday learning cards, then review cards, and finally new cards. This affects
how burying works: how burying works:
- If you have all burying options enabled, the sibling that comes earliest in - If you have all burying options enabled, the sibling that comes earliest in
that list will be shown. For example, a review card will be shown in preference that list will be shown. For example, a review card will be shown in preference
to a new card. to a new card.
@ -141,18 +141,21 @@ deck-config-new-gather-priority-tooltip-2 =
gathered in ascending position. If the daily limit of the selected deck is reached, gathering gathered in ascending position. If the daily limit of the selected deck is reached, gathering
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.
`Descending position`: Gathers cards by descending position (due #), which is typically `Descending position`: Gathers cards by descending position (due #), which is typically
the latest-added first. the latest-added first.
`Random notes`: Picks notes at random, then gathers all of its cards. `Random notes`: Picks notes at random, then gathers all of its cards.
`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
@ -160,21 +163,21 @@ deck-config-new-gather-priority-random-cards = Random cards
deck-config-new-card-sort-order = New card sort order deck-config-new-card-sort-order = New card sort order
deck-config-new-card-sort-order-tooltip-2 = deck-config-new-card-sort-order-tooltip-2 =
`Card type, then order gathered`: Shows cards in order of card type number. `Card type, then order gathered`: Shows cards in order of card type number.
Cards of each card type number are shown in the order they were gathered. Cards of each card type number are shown in the order they were gathered.
If you have sibling burying disabled, this will ensure all front→back cards are seen before any back→front cards. If you have sibling burying disabled, this will ensure all front→back cards are seen before any back→front cards.
This is useful to have all cards of the same note shown in the same session, but not This is useful to have all cards of the same note shown in the same session, but not
too close to one another. too close to one another.
`Order gathered`: Shows cards exactly as they were gathered. If sibling burying is disabled, `Order gathered`: Shows cards exactly as they were gathered. If sibling burying is disabled,
this will typically result in all cards of a note being seen one after the other. this will typically result in all cards of a note being seen one after the other.
`Card type, then random`: Shows cards in order of card type number. Cards of each card `Card type, then random`: Shows cards in order of card type number. Cards of each card
type number are shown in a random order. This order is useful if you don't want sibling cards type number are shown in a random order. This order is useful if you don't want sibling cards
to appear too close to each other, but still want the cards to appear in a random order. to appear too close to each other, but still want the cards to appear in a random order.
`Random note, then card type`: Picks notes at random, then shows all of its cards `Random note, then card type`: Picks notes at random, then shows all of its cards
in order. in order.
`Random`: Shows cards in a random order. `Random`: Shows cards in a random order.
deck-config-sort-order-card-template-then-random = Card type, then random deck-config-sort-order-card-template-then-random = Card type, then random
deck-config-sort-order-random-note-then-template = Random note, then card type deck-config-sort-order-random-note-then-template = Random note, then card type
@ -186,7 +189,7 @@ deck-config-new-review-priority-tooltip = When to show new cards in relation to
deck-config-interday-step-priority = Interday learning/review order deck-config-interday-step-priority = Interday learning/review order
deck-config-interday-step-priority-tooltip = deck-config-interday-step-priority-tooltip =
When to show (re)learning cards that cross a day boundary. When to show (re)learning cards that cross a day boundary.
The review limit is always applied first to interday learning cards, and The review limit is always applied first to interday learning cards, and
then review cards. This option will control the order the gathered cards are shown in, then review cards. This option will control the order the gathered cards are shown in,
but interday learning cards will always be gathered first. but interday learning cards will always be gathered first.
@ -213,7 +216,7 @@ deck-config-sort-order-retrievability-ascending = Ascending retrievability
deck-config-sort-order-retrievability-descending = Descending retrievability deck-config-sort-order-retrievability-descending = Descending retrievability
deck-config-display-order-will-use-current-deck = deck-config-display-order-will-use-current-deck =
Anki will use the display order from the deck you Anki will use the display order from the deck you
select to study, and not any subdecks it may have. select to study, and not any subdecks it may have.
## Timer section ## Timer section
@ -240,7 +243,7 @@ deck-config-seconds-to-show-answer = Seconds to show answer for
deck-config-seconds-to-show-answer-tooltip-2 = When auto advance is activated, the number of seconds to wait before applying the answer action. Set to 0 to disable. deck-config-seconds-to-show-answer-tooltip-2 = When auto advance is activated, the number of seconds to wait before applying the answer action. Set to 0 to disable.
deck-config-question-action-show-answer = Show Answer deck-config-question-action-show-answer = Show Answer
deck-config-question-action-show-reminder = Show Reminder deck-config-question-action-show-reminder = Show Reminder
deck-config-question-action = Question action deck-config-question-action = Question action
deck-config-question-action-tool-tip = The action to perform after the question is shown, and time has elapsed. deck-config-question-action-tool-tip = The action to perform after the question is shown, and time has elapsed.
deck-config-answer-action = Answer action deck-config-answer-action = Answer action
deck-config-answer-action-tooltip-2 = The action to perform after the answer is shown, and time has elapsed. deck-config-answer-action-tooltip-2 = The action to perform after the answer is shown, and time has elapsed.
@ -405,7 +408,7 @@ deck-config-historical-retention-tooltip =
The latter is quite rare, so unless you're using the former option, you probably don't need to adjust The latter is quite rare, so unless you're using the former option, you probably don't need to adjust
this option. this option.
deck-config-weights-tooltip2 = deck-config-weights-tooltip2 =
FSRS parameters affect how cards are scheduled. Anki will start with default parameters. You can use FSRS parameters affect how cards are scheduled. Anki will start with default parameters. You can use
the option below to optimize the parameters to best match your performance in decks using this preset. the option below to optimize the parameters to best match your performance in decks using this preset.
deck-config-reschedule-cards-on-change-tooltip = deck-config-reschedule-cards-on-change-tooltip =
Affects the entire collection, and is not saved. Affects the entire collection, and is not saved.
@ -420,31 +423,31 @@ deck-config-reschedule-cards-warning =
Use this option sparingly, as it will add a review entry to each of your cards, and Use this option sparingly, as it will add a review entry to each of your cards, and
increase the size of your collection. increase the size of your collection.
deck-config-ignore-before-tooltip-2 = deck-config-ignore-before-tooltip-2 =
If set, cards reviewed before the provided date will be ignored when optimizing FSRS parameters. If set, cards reviewed before the provided date will be ignored when optimizing FSRS parameters.
This can be useful if you imported someone else's scheduling data, or have changed the way you use the answer buttons. This can be useful if you imported someone else's scheduling data, or have changed the way you use the answer buttons.
deck-config-compute-optimal-weights-tooltip2 = deck-config-compute-optimal-weights-tooltip2 =
When you click the Optimize button, FSRS will analyze your review history, and generate parameters that are When you click the Optimize button, FSRS will analyze your review history, and generate parameters that are
optimal for your memory and the content you're studying. If your decks vary wildly in subjective difficulty, it optimal for your memory and the content you're studying. If your decks vary wildly in subjective difficulty, it
is recommended to assign them separate presets, as the parameters for easy decks and hard decks will be different. is recommended to assign them separate presets, as the parameters for easy decks and hard decks will be different.
You don't need to optimize your parameters frequently - once every few months is sufficient. You don't need to optimize your parameters frequently - once every few months is sufficient.
By default, parameters will be calculated from the review history of all decks using the current preset. You can By default, parameters will be calculated from the review history of all decks using the current preset. You can
optionally adjust the search before calculating the parameters, if you'd like to alter which cards are used for optionally adjust the search before calculating the parameters, if you'd like to alter which cards are used for
optimizing the parameters. optimizing the parameters.
deck-config-compute-optimal-retention-tooltip4 = deck-config-compute-optimal-retention-tooltip4 =
This tool will attempt to find the desired retention value This tool will attempt to find the desired retention value
that will lead to the most material learnt, in the least amount of time. The calculated number can serve as a reference that will lead to the most material learnt, in the least amount of time. The calculated number can serve as a reference
when deciding what to set your desired retention to. You may wish to choose a higher desired retention if youre when deciding what to set your desired retention to. You may wish to choose a higher desired retention if youre
willing to invest more study time to achieve it. Setting your desired retention lower than the minimum willing to invest more study time to achieve it. Setting your desired retention lower than the minimum
is not recommended, as it will lead to a higher workload, because of the high forgetting rate. is not recommended, as it will lead to a higher workload, because of the high forgetting rate.
deck-config-please-save-your-changes-first = Please save your changes first. deck-config-please-save-your-changes-first = Please save your changes first.
deck-config-a-100-day-interval = deck-config-a-100-day-interval =
{ $days -> { $days ->
[one] A 100 day interval will become { $days } day. [one] A 100 day interval will become { $days } day.
*[other] A 100 day interval will become { $days } days. *[other] A 100 day interval will become { $days } days.
} }
deck-config-percent-of-reviews = deck-config-percent-of-reviews =
{ $reviews -> { $reviews ->
[one] { $pct }% of { $reviews } review [one] { $pct }% of { $reviews } review
*[other] { $pct }% of { $reviews } reviews *[other] { $pct }% of { $reviews } reviews
@ -488,18 +491,18 @@ deck-config-bury-if-new-review-or-interday = Bury if new, review, or interday le
deck-config-bury-tooltip = deck-config-bury-tooltip =
Siblings are other cards from the same note (eg forward/reverse cards, or Siblings are other cards from the same note (eg forward/reverse cards, or
other cloze deletions from the same text). other cloze deletions from the same text).
When this option is off, multiple cards from the same note may be seen on the same When this option is off, multiple cards from the same note may be seen on the same
day. When enabled, Anki will automatically *bury* siblings, hiding them until the next day. When enabled, Anki will automatically *bury* siblings, hiding them until the next
day. This option allows you to choose which kinds of cards may be buried when you answer day. This option allows you to choose which kinds of cards may be buried when you answer
one of their siblings. one of their siblings.
When using the V3 scheduler, interday learning cards can also be buried. Interday When using the V3 scheduler, interday learning cards can also be buried. Interday
learning cards are cards with a current learning step of one or more days. learning cards are cards with a current learning step of one or more days.
deck-config-seconds-to-show-question-tooltip = When auto advance is activated, the number of seconds to wait before revealing the answer. Set to 0 to disable. deck-config-seconds-to-show-question-tooltip = When auto advance is activated, the number of seconds to wait before revealing the answer. Set to 0 to disable.
deck-config-answer-action-tooltip = The action to perform on the current card before automatically advancing to the next one. deck-config-answer-action-tooltip = The action to perform on the current card before automatically advancing to the next one.
deck-config-wait-for-audio-tooltip = Wait for audio to finish before automatically revealing answer or next question. deck-config-wait-for-audio-tooltip = Wait for audio to finish before automatically revealing answer or next question.
deck-config-ignore-before-tooltip = deck-config-ignore-before-tooltip =
If set, reviews before the provided date will be ignored when optimizing & evaluating FSRS parameters. If set, reviews before the provided date will be ignored when optimizing & evaluating FSRS parameters.
This can be useful if you imported someone else's scheduling data, or have changed the way you use the answer buttons. This can be useful if you imported someone else's scheduling data, or have changed the way you use the answer buttons.
deck-config-compute-optimal-retention-tooltip = deck-config-compute-optimal-retention-tooltip =
@ -521,7 +524,7 @@ deck-config-compute-optimal-weights-tooltip =
If you have decks that vary wildly in difficulty, it is recommended to assign them separate presets, as If you have decks that vary wildly in difficulty, it is recommended to assign them separate presets, as
the parameters for easy decks and hard decks will be different. There is no need to optimize your parameters the parameters for easy decks and hard decks will be different. There is no need to optimize your parameters
frequently - once every few months is sufficient. frequently - once every few months is sufficient.
By default, parameters will be calculated from the review history of all decks using the current preset. You can By default, parameters will be calculated from the review history of all decks using the current preset. You can
optionally adjust the search before calculating the parameters, if you'd like to alter which cards are used for optionally adjust the search before calculating the parameters, if you'd like to alter which cards are used for
optimizing the parameters. optimizing the parameters.
@ -532,11 +535,11 @@ deck-config-compute-optimal-retention-tooltip2 =
if youre willing to trade more study time for a greater recall rate. Setting your desired retention lower than if youre willing to trade more study time for a greater recall rate. Setting your desired retention lower than
the minimum is not recommended, as it will lead to more work without benefit. the minimum is not recommended, as it will lead to more work without benefit.
deck-config-compute-optimal-retention-tooltip3 = deck-config-compute-optimal-retention-tooltip3 =
This tool assumes that youre starting with 0 learned cards, and will attempt to find the desired retention value This tool assumes that youre starting with 0 learned cards, and will attempt to find the desired retention value
that will lead to the most material learnt, in the least amount of time. To accurately simulate your learning process, that will lead to the most material learnt, in the least amount of time. To accurately simulate your learning process,
this feature requires a minimum of 400+ reviews. The calculated number can serve as a reference when deciding what to this feature requires a minimum of 400+ reviews. The calculated number can serve as a reference when deciding what to
set your desired retention to. You may wish to choose a higher desired retention, if youre willing to trade more study set your desired retention to. You may wish to choose a higher desired retention, if youre willing to trade more study
time for a greater recall rate. Setting your desired retention lower than the minimum is not recommended, as it will time for a greater recall rate. Setting your desired retention lower than the minimum is not recommended, as it will
lead to a higher workload, because of the high forgetting rate. lead to a higher workload, because of the high forgetting rate.
deck-config-seconds-to-show-question-tooltip-2 = When auto advance is activated, the number of seconds to wait before revealing the answer. Set to 0 to disable. deck-config-seconds-to-show-question-tooltip-2 = When auto advance is activated, the number of seconds to wait before revealing the answer. Set to 0 to disable.
deck-config-invalid-weights = Parameters must be either left blank to use the defaults, or must be 17 comma-separated numbers. deck-config-invalid-weights = Parameters must be either left blank to use the defaults, or must be 17 comma-separated numbers.

@ -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,