Skip to content

Instantly share code, notes, and snippets.

@teryror
Last active September 30, 2020 09:32
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save teryror/6daaba88896ab6fbeb493105f4343fea to your computer and use it in GitHub Desktop.
Save teryror/6daaba88896ab6fbeb493105f4343fea to your computer and use it in GitHub Desktop.
Simulation code analyzing how mana bases affect mulligan decisions in Magic: the Gathering
use rand::prelude::*;
use std::collections::HashMap;
use std::fmt::Write;
#[derive(Copy, Clone, PartialEq, Eq)]
enum CardType {
NonLand,
Land,
GoodLand,
}
#[derive(Copy, Clone)]
struct Deck {
total_cards: u32,
total_lands: u32,
good_lands: u32
}
#[derive(Copy, Clone)]
struct OpeningHand {
lands_in_hand: u32,
good_lands_in_hand: u32,
}
// A mulligan strategy decides whether or not to keep a hand, and if so, which cards to bottom
type MullStrat = fn(OpeningHand, u32) -> Option<OpeningHand>;
impl Deck {
fn new(total_cards: u32, total_lands: u32, good_lands: u32) -> Self {
Deck { total_cards, total_lands, good_lands }
}
fn draw_card(&mut self, rng: &mut ThreadRng) -> CardType {
let int_between_one_and_deck_size = rng.gen_range(0, self.total_cards) + 1;
let good_land_cutoff = self.good_lands;
let land_cutoff = self.total_lands;
if int_between_one_and_deck_size <= good_land_cutoff {
self.total_cards -= 1;
self.total_lands -= 1;
self.good_lands -= 1;
CardType::GoodLand
} else if int_between_one_and_deck_size > good_land_cutoff && int_between_one_and_deck_size <= land_cutoff {
self.total_cards -= 1;
self.total_lands -= 1;
CardType::Land
} else if int_between_one_and_deck_size > land_cutoff {
self.total_cards -= 1;
CardType::NonLand
} else {
unreachable!()
}
}
fn draw_opening_hand(&mut self, rng: &mut ThreadRng) -> OpeningHand {
let mut lands_in_hand = 0;
let mut good_lands_in_hand = 0;
for _ in 0..7 {
match self.draw_card(rng) {
CardType::GoodLand => {
lands_in_hand += 1;
good_lands_in_hand += 1;
},
CardType::Land => {
lands_in_hand += 1;
},
CardType::NonLand => {}
}
}
OpeningHand {
lands_in_hand,
good_lands_in_hand
}
}
}
fn run_sim(deck_size: u32, land_count: u32, colored_sources: u32, mull_strategy: MullStrat, max_cmc: u32) {
assert!(deck_size >= land_count);
assert!(land_count >= colored_sources);
const NUM_ITERATIONS: u32 = 10_000_000;
let mut rng = thread_rng();
let mut sum_of_all_starting_hand_sizes = 0;
let mut freq_curve_out = vec![0u32; max_cmc as usize].into_boxed_slice();
let mut freq_ok_to_cast = HashMap::new();
for _ in 0..NUM_ITERATIONS {
let mut deck = Deck::new(deck_size, land_count, colored_sources);
let mut starting_hand_size = 7;
let mut free_mulligan = deck_size == 99;
let OpeningHand { mut lands_in_hand, mut good_lands_in_hand } = loop {
let opened = deck.draw_opening_hand(&mut rng);
if let Some(kept_hand) = mull_strategy(opened, starting_hand_size) {
break kept_hand;
}
if free_mulligan {
free_mulligan = false;
continue;
}
starting_hand_size -= 1;
};
sum_of_all_starting_hand_sizes += starting_hand_size;
for turn_allowed in 1..=max_cmc {
if turn_allowed > 1 || deck_size == 99 {
let card_type = deck.draw_card(&mut rng);
if card_type != CardType::NonLand {
lands_in_hand += 1;
if card_type == CardType::GoodLand {
good_lands_in_hand += 1;
}
}
}
if lands_in_hand >= turn_allowed {
freq_curve_out[turn_allowed as usize - 1] += 1;
for num_good_lands_needed in 1..=turn_allowed {
if good_lands_in_hand >= num_good_lands_needed {
*freq_ok_to_cast.entry((num_good_lands_needed, turn_allowed)).or_insert(0u32) += 1;
}
}
}
}
}
print!("| {:7} | ", colored_sources);
for turn_allowed in 1..=max_cmc {
let count_conditional = freq_curve_out[turn_allowed as usize - 1];
let consistency_cutoff = (90 + turn_allowed) as f64;
for num_good_lands_needed in 1..=turn_allowed {
let count_ok = freq_ok_to_cast[&(num_good_lands_needed, turn_allowed)];
let percentage = count_ok as f64 / count_conditional as f64 * 100.0;
if percentage >= consistency_cutoff {
let highlighted = format!("**{:.2}%**", percentage);
print!("{:11} | ", highlighted);
} else {
print!("{:10.2}% | ", percentage);
}
}
}
let mean_starting_hand_size = sum_of_all_starting_hand_sizes as f64 / NUM_ITERATIONS as f64;
println!("{:4.3} cards |", mean_starting_hand_size);
}
fn naive_mulligan_strategy(opened: OpeningHand, starting_hand_size: u32) -> Option<OpeningHand> {
let mut hand = opened;
for _ in starting_hand_size..7 {
if hand.lands_in_hand > starting_hand_size / 2 {
hand.lands_in_hand -= 1;
if hand.good_lands_in_hand > hand.lands_in_hand {
hand.good_lands_in_hand -= 1;
}
}
if hand.lands_in_hand < hand.good_lands_in_hand {
dbg!(starting_hand_size);
dbg!(hand.lands_in_hand);
dbg!(hand.good_lands_in_hand);
}
assert!(hand.lands_in_hand >= hand.good_lands_in_hand);
}
match starting_hand_size {
7 => {
if hand.lands_in_hand < 2 || hand.lands_in_hand >= 6 {
None
} else {
Some(hand)
}
},
6 => {
if hand.lands_in_hand < 2 || hand.lands_in_hand >= 5 {
None
} else {
Some(hand)
}
},
5 => {
if hand.lands_in_hand == 0 || hand.lands_in_hand == 5 {
None
} else {
Some(hand)
}
},
_ => Some(hand)
}
}
fn color_aware_mulligan_strategy(opened: OpeningHand, starting_hand_size: u32) -> Option<OpeningHand> {
naive_mulligan_strategy(opened, starting_hand_size)
.filter(|hand| starting_hand_size < 6 || hand.good_lands_in_hand >= 1)
}
fn print_table_header(max_cmc: u32) {
print!("| Sources | ");
for turn_allowed in 1..=max_cmc {
for num_good_lands_needed in 1..=turn_allowed {
let mut mana_cost = String::with_capacity(num_good_lands_needed as usize + 1);
if turn_allowed > num_good_lands_needed {
write!(&mut mana_cost, "{}", turn_allowed - num_good_lands_needed).unwrap();
}
for _ in 1..=num_good_lands_needed {
mana_cost.push('C');
}
print!("{:11} | ", mana_cost);
}
}
println!("mean hand |");
print!("|--------:|");
for turn_allowed in 1..=max_cmc {
for _ in 1..=turn_allowed {
print!("------------:|");
}
}
println!(":-----------|");
}
fn main() {
println!("## Naive mulligan strategy");
print_table_header(5);
for colored_sources in 6..=24 {
run_sim(60, 24, colored_sources, naive_mulligan_strategy, 5);
}
println!();
println!("## Color-aware mulligan strategy");
print_table_header(5);
for colored_sources in 6..=24 {
run_sim(60, 24, colored_sources, color_aware_mulligan_strategy, 5);
}
}
const NUM_ITERATIONS: u32 = 5_000_000;
mod mono_color {
#[derive(Copy, Clone, Eq, PartialEq)]
enum CardType {
NonLand,
ColoredLand,
ColorlessLand,
}
#[derive(Copy, Clone)]
struct MonoColorDeck {
total_cards: u32,
total_lands: u32,
color_lands: u32,
}
impl MonoColorDeck {
fn new(deck_size: u32, total_lands: u32, colored_sources: u32) -> Self {
MonoColorDeck {
total_cards: deck_size,
total_lands,
color_lands: colored_sources,
}
}
fn draw_card(&mut self) -> CardType {
use rand::prelude::*;
let mut rng = thread_rng();
let random_int_between_one_and_deck_size = rng.gen_range(0, self.total_cards) + 1;
if random_int_between_one_and_deck_size <= self.color_lands {
self.color_lands -= 1;
self.total_lands -= 1;
self.total_cards -= 1;
CardType::ColoredLand
} else if random_int_between_one_and_deck_size <= self.total_lands {
self.total_lands -= 1;
self.total_cards -= 1;
CardType::ColorlessLand
} else {
self.total_cards -= 1;
CardType::NonLand
}
}
fn draw_seven(&mut self) -> (u32, u32) {
let mut lands_drawn_total = 0;
let mut colored_lands_drawn = 0;
for _ in 0..7 {
let card_type = self.draw_card();
if card_type != CardType::NonLand {
lands_drawn_total += 1;
if card_type == CardType::ColoredLand {
colored_lands_drawn += 1;
}
}
}
(colored_lands_drawn, lands_drawn_total)
}
}
fn run_sim(deck_size: u32, total_lands: u32) {
use super::NUM_ITERATIONS;
let conditions_on_total_lands = [2..=3, 2..=4, 3..=4];
let consistency_cutoff = 0.95;
let mut minima_one_or_more = [total_lands; 3];
let mut minima_two_or_more = [total_lands; 3];
for num_colored_sources in (1..total_lands).rev() {
let mut count_one_or_more_colored_sources = [0, 0, 0];
let mut count_two_or_more_colored_sources = [0, 0, 0];
let mut count_conditional = [0, 0, 0];
for _ in 0..NUM_ITERATIONS {
let mut deck = MonoColorDeck::new(deck_size, total_lands, num_colored_sources);
let (colored_lands_drawn, lands_drawn_total) = deck.draw_seven();
for (i, condition) in conditions_on_total_lands.iter().enumerate() {
if condition.contains(&lands_drawn_total) {
count_conditional[i] += 1;
if colored_lands_drawn >= 1 {
count_one_or_more_colored_sources[i] += 1;
if colored_lands_drawn >= 2 {
count_two_or_more_colored_sources[i] += 1;
}
}
}
}
}
let mut all_freqs_below_cutoff = true;
for i in 0..3 {
let count_one_or_more = count_one_or_more_colored_sources[i];
let count_two_or_more = count_two_or_more_colored_sources[i];
let freq_one_or_more = count_one_or_more as f64 / count_conditional[i] as f64;
let freq_two_or_more = count_two_or_more as f64 / count_conditional[i] as f64;
if freq_one_or_more >= consistency_cutoff {
minima_one_or_more[i] = num_colored_sources;
all_freqs_below_cutoff = false;
}
if freq_two_or_more >= consistency_cutoff {
minima_two_or_more[i] = num_colored_sources;
all_freqs_below_cutoff = false;
}
}
if all_freqs_below_cutoff {
break;
}
}
print!("| {:5} | ", total_lands);
print!("{:4} / {:2} | ", minima_one_or_more[0], minima_two_or_more[0]);
print!("{:4} / {:2} | ", minima_one_or_more[1], minima_two_or_more[1]);
print!("{:4} / {:2} |", minima_one_or_more[2], minima_two_or_more[2]);
println!();
}
pub fn run_all_sims() {
println!("# Mono-colored decks");
println!("## Limited decks (40 cards)");
println!("| Lands | 2-3 lands | 2-4 lands | 3-4 lands |");
println!("|------:|----------:|----------:|----------:|");
for num_lands in 10..=20 {
run_sim(40, num_lands);
}
println!();
println!("## Constructed decks (60 cards)");
println!("| Lands | 2-3 lands | 2-4 lands | 3-4 lands |");
println!("|------:|----------:|----------:|----------:|");
for num_lands in 15..=30 {
run_sim(60, num_lands);
}
println!();
println!("## Yorion decks (80 cards)");
println!("| Lands | 2-3 lands | 2-4 lands | 3-4 lands |");
println!("|------:|----------:|----------:|----------:|");
for num_lands in 20..=40 {
run_sim(80, num_lands);
}
println!();
println!("## Commander decks (99 cards)");
println!("| Lands | 2-3 lands | 2-4 lands | 3-4 lands |");
println!("|------:|----------:|----------:|----------:|");
for num_lands in 24..=49 {
run_sim(99, num_lands);
}
}
}
mod dual_color {
use rand::{thread_rng, Rng};
#[derive(Copy, Clone, Eq, PartialEq)]
enum CardType {
NonLand,
ColorlessLand,
MonoColorLandA,
MonoColorLandB,
DualColorLand,
}
#[derive(Copy, Clone)]
struct Deck {
total_cards: u32,
cards_by_card_type: [u32; 5],
}
impl Deck {
fn new(total_cards: u32, colorless_sources: u32, colored_sources_a: u32, colored_sources_b: u32, two_color_sources: u32) -> Self {
let non_land_cards = total_cards - (colorless_sources + colored_sources_a + colored_sources_b + two_color_sources);
Deck {
total_cards,
cards_by_card_type: [non_land_cards, colorless_sources, colored_sources_a, colored_sources_b, two_color_sources],
}
}
fn draw_card(&mut self) -> CardType {
let mut rng = thread_rng();
let mut random_int_between_one_and_deck_size = rng.gen_range(0, self.total_cards) + 1;
if random_int_between_one_and_deck_size <= self.cards_by_card_type[0] {
self.total_cards -= 1;
self.cards_by_card_type[0] -= 1;
return CardType::NonLand;
}
random_int_between_one_and_deck_size -= self.cards_by_card_type[0];
if random_int_between_one_and_deck_size <= self.cards_by_card_type[1] {
self.total_cards -= 1;
self.cards_by_card_type[1] -= 1;
return CardType::ColorlessLand;
}
random_int_between_one_and_deck_size -= self.cards_by_card_type[1];
if random_int_between_one_and_deck_size <= self.cards_by_card_type[2] {
self.total_cards -= 1;
self.cards_by_card_type[2] -= 1;
return CardType::MonoColorLandA;
}
random_int_between_one_and_deck_size -= self.cards_by_card_type[2];
if random_int_between_one_and_deck_size <= self.cards_by_card_type[3] {
self.total_cards -= 1;
self.cards_by_card_type[3] -= 1;
return CardType::MonoColorLandB;
}
random_int_between_one_and_deck_size -= self.cards_by_card_type[3];
if random_int_between_one_and_deck_size <= self.cards_by_card_type[4] {
self.total_cards -= 1;
self.cards_by_card_type[4] -= 1;
return CardType::DualColorLand;
}
unreachable!()
}
}
fn run_sim(deck_size: u32, land_count: u32, max_off_color_land_count: u32, off_color_land_count_step: usize) {
use super::NUM_ITERATIONS;
let conditions_on_total_lands = [2..=3, 2..=4, 3..=4];
print!("| {:5} |", land_count);
for num_off_color_lands in (0..=max_off_color_land_count).into_iter().step_by(off_color_land_count_step) {
let num_on_color_lands = land_count - num_off_color_lands;
let mut num_duals_minima = [u32::max_value(); 3];
for num_dual_lands in (0..=num_on_color_lands).rev() {
let other_lands = num_on_color_lands - num_dual_lands;
let num_mono_color_lands_b = other_lands / 2;
let num_mono_color_lands_a = other_lands - num_mono_color_lands_b;
let mut count_conditional = [0; 3];
let mut count_ok = [0; 3];
for _ in 0..NUM_ITERATIONS {
let mut deck = Deck::new(
deck_size,
num_off_color_lands,
num_mono_color_lands_a,
num_mono_color_lands_b,
num_dual_lands);
let mut total_lands_drawn = 0;
let mut sources_drawn_a = 0;
let mut sources_drawn_b = 0;
for _ in 0..7 {
let card_type = deck.draw_card();
if card_type != CardType::NonLand {
total_lands_drawn += 1;
if card_type == CardType::MonoColorLandA || card_type == CardType::DualColorLand {
sources_drawn_a += 1;
}
if card_type == CardType::MonoColorLandB || card_type == CardType::DualColorLand {
sources_drawn_b += 1;
}
}
}
for (i, condition) in conditions_on_total_lands.iter().enumerate() {
if condition.contains(&total_lands_drawn) {
count_conditional[i] += 1;
if sources_drawn_a > 0 && sources_drawn_b > 0 {
count_ok[i] += 1;
}
}
}
}
let mut all_failed = true;
for (i, (count_ok, count_conditional)) in count_ok.iter().zip(count_conditional.iter()).enumerate() {
let freq_ok = *count_ok as f64 / *count_conditional as f64;
if freq_ok > 0.95 {
num_duals_minima[i] = num_dual_lands;
all_failed = false;
}
}
if all_failed { break; }
}
print!(" {:2} / {:2} / {:2} |", num_duals_minima[0], num_duals_minima[1], num_duals_minima[2]);
}
println!();
}
pub fn run_all_sims() {
println!("# Two-color Manabases");
println!("## Limited decks (40 cards):");
println!("| Lands | 0 off-color | 1 off-color | 2 off-color | 3 off-color |");
println!("|------:|-------------:|-------------:|-------------:|-------------:|");
for land_count in 10..=20 {
run_sim(40, land_count, 3, 1);
}
println!();
println!("## Constructed decks (60 cards):");
println!("| Lands | 0 off-color | 1 off-color | 2 off-color | 3 off-color | 4 off-color |");
println!("|------:|-------------:|-------------:|-------------:|-------------:|-------------:|");
for land_count in 15..=30 {
run_sim(60, land_count, 4, 1);
}
println!();
println!("## Yorion decks (80 cards):");
println!("| Lands | 0 off-color | 1 off-color | 2 off-color | 3 off-color | 4 off-color |");
println!("|------:|-------------:|-------------:|-------------:|-------------:|-------------:|");
for land_count in 20..=40 {
run_sim(80, land_count, 4, 1);
}
println!();
println!("## Commander decks (99 cards):");
println!("| Lands | 0 off-color | 2 off-color | 4 off-color | 6 off-color | 8 off-color |");
println!("|------:|-------------:|-------------:|-------------:|-------------:|-------------:|");
for land_count in 24..=49 {
run_sim(99, land_count, 8, 2);
}
}
}
mod three_colors {
use rand::Rng;
#[repr(usize)]
#[derive(Copy, Clone, Eq, PartialEq)]
enum CardType {
NonLand = 0,
FabledPassage = 1,
BasicLandA = 2,
BasicLandB = 3,
BasicLandC = 4,
DualLandAB = 5,
DualLandAC = 6,
DualLandBC = 7,
TriLand = 8,
}
#[derive(Copy, Clone)]
struct Deck {
total_cards: u32,
num_cards_by_type: [u32; 9]
}
impl Deck {
fn with_balanced_duals(deck_size: u32, land_count: u32, num_tri_lands: u32) -> Deck {
let non_land_cards = deck_size - land_count;
let other_lands = land_count - num_tri_lands;
let dual_lands_ab = other_lands / 3 + if other_lands % 3 > 0 { 1 } else { 0 };
let dual_lands_ac = other_lands / 3 + if other_lands % 3 > 1 { 1 } else { 0 };
let dual_lands_bc = other_lands / 3;
assert_eq!(land_count, dual_lands_ab + dual_lands_ac + dual_lands_bc + num_tri_lands);
Deck {
total_cards: deck_size,
num_cards_by_type: [non_land_cards, 0, 0, 0, 0, dual_lands_ab, dual_lands_ac, dual_lands_bc, num_tri_lands],
}
}
fn with_skewed_duals(deck_size: u32, land_count: u32, num_tri_lands: u32) -> Deck {
let non_land_cards = deck_size - land_count;
let other_lands = land_count - num_tri_lands;
let dual_lands_ab = other_lands / 2;
let dual_lands_ac = other_lands - dual_lands_ab;
assert_eq!(land_count, num_tri_lands + dual_lands_ab + dual_lands_ac);
Deck {
total_cards: deck_size,
num_cards_by_type: [non_land_cards, 0, 0, 0, 0, dual_lands_ab, dual_lands_ac, 0, num_tri_lands],
}
}
fn with_basics_and_fabled_passage(deck_size: u32, land_count: u32, num_tri_lands: u32) -> Deck {
let non_land_cards = deck_size - land_count;
let other_lands = land_count - (num_tri_lands + 10); // account for Fabled Passage + fetchable basics
let dual_lands_ab = other_lands / 3 + if other_lands % 3 > 0 { 1 } else { 0 };
let dual_lands_ac = other_lands / 3 + if other_lands % 3 > 1 { 1 } else { 0 };
let dual_lands_bc = other_lands / 3;
assert_eq!(land_count, num_tri_lands + dual_lands_ab + dual_lands_ac + dual_lands_bc + 10);
Deck {
total_cards: deck_size,
num_cards_by_type: [non_land_cards, 4, 2, 2, 2, dual_lands_ab, dual_lands_ac, dual_lands_bc, num_tri_lands],
}
}
fn with_basics_and_fetch_lands(deck_size: u32, land_count: u32, num_basics: u32) -> Deck {
// For simplicity, we'll count proper fetches as tri-lands,
// since they're just as good at fixing opening hands as long as there's no colorless lands
let non_land_cards = deck_size - land_count;
let other_lands = land_count - (8 + num_basics);
let dual_lands_ab = other_lands / 3 + if other_lands % 3 > 0 { 1 } else { 0 };
let dual_lands_ac = other_lands / 3 + if other_lands % 3 > 1 { 1 } else { 0 };
let dual_lands_bc = other_lands / 3;
let basics = if num_basics == 5 {
(3, 1, 1)
} else if num_basics == 6 {
(2, 2, 2)
} else {
unreachable!()
};
assert_eq!(land_count, 8 + num_basics + dual_lands_ab + dual_lands_ac + dual_lands_bc);
Deck {
total_cards: deck_size,
num_cards_by_type: [non_land_cards, 0, basics.0, basics.1, basics.2, dual_lands_ab, dual_lands_ac, dual_lands_bc, 8],
}
}
fn draw_card(&mut self) -> CardType {
let mut random_int_between_one_and_deck_size = rand::thread_rng().gen_range(0, self.total_cards) + 1;
for (idx, count) in self.num_cards_by_type.iter_mut().enumerate() {
if random_int_between_one_and_deck_size <= *count {
self.total_cards -= 1;
*count -= 1;
return unsafe { std::mem::transmute(idx) };
}
random_int_between_one_and_deck_size -= *count;
}
unreachable!()
}
fn draw_seven(&mut self) -> (u32, bool) {
let mut total_lands_drawn = 0;
let mut sources_drawn_a = 0;
let mut sources_drawn_b = 0;
let mut sources_drawn_c = 0;
let mut passages_drawn = 0;
for _ in 0..7 {
use CardType::*;
let card_type = self.draw_card();
if card_type == NonLand { continue; }
total_lands_drawn += 1;
if card_type == FabledPassage {
passages_drawn += 1;
}
if card_type == BasicLandA || card_type == DualLandAB || card_type == DualLandAC || card_type == TriLand {
sources_drawn_a += 1;
}
if card_type == BasicLandB || card_type == DualLandAB || card_type == DualLandBC || card_type == TriLand {
sources_drawn_b += 1;
}
if card_type == BasicLandC || card_type == DualLandAC || card_type == DualLandBC || card_type == TriLand {
sources_drawn_c += 1;
}
}
while passages_drawn > 0 {
passages_drawn -= 1;
if sources_drawn_a == 0 {
sources_drawn_a += 1;
} else if sources_drawn_b == 0 {
sources_drawn_b += 1;
} else {
sources_drawn_c += 1;
}
}
let has_all_colors = sources_drawn_a > 0 && sources_drawn_b > 0 && sources_drawn_c > 0;
(total_lands_drawn, has_all_colors)
}
}
fn run_sim(deck_size: u32, land_count: u32, num_tri_lands_to_compare_to_zero: u32) {
let decks_to_test = [
Deck::with_balanced_duals(deck_size, land_count, 0),
Deck::with_balanced_duals(deck_size, land_count, num_tri_lands_to_compare_to_zero),
Deck::with_skewed_duals(deck_size, land_count, 0),
Deck::with_skewed_duals(deck_size, land_count, num_tri_lands_to_compare_to_zero),
Deck::with_basics_and_fabled_passage(deck_size, land_count, 0),
Deck::with_basics_and_fabled_passage(deck_size, land_count, 4),
Deck::with_basics_and_fetch_lands(deck_size, land_count, 5),
Deck::with_basics_and_fetch_lands(deck_size, land_count, 6),
];
let conditions_on_lands_in_hand = [2..=3, 2..=4, 3..=4];
print!("| {:5} |", land_count);
for deck_to_test in &decks_to_test {
let mut count_conditional = [0; 3];
let mut count_ok = [0; 3];
for _ in 0..super::NUM_ITERATIONS {
let mut deck = *deck_to_test;
assert_eq!(deck.total_cards, deck_size);
let (total_lands_drawn, has_all_colors) = deck.draw_seven();
for (i, condition) in conditions_on_lands_in_hand.iter().enumerate() {
if condition.contains(&total_lands_drawn) {
count_conditional[i] += 1;
if has_all_colors {
count_ok[i] += 1;
}
}
}
}
let freq_ok = count_ok[0] as f64 / count_conditional[0] as f64 * 100.0;
print!(" {:5.1}% / ", freq_ok);
let freq_ok = count_ok[1] as f64 / count_conditional[1] as f64 * 100.0;
print!("{:5.1}% / ", freq_ok);
let freq_ok = count_ok[2] as f64 / count_conditional[2] as f64 * 100.0;
print!("{:5.1}% |", freq_ok)
}
println!();
}
pub fn run_all_sims() {
println!("# Tri-color Manabases");
println!("## Constructed decks (60 cards):");
println!("| Lands | even duals, no triomes | even duals, 4 triomes | skewed duals, no triomes | skewed duals, 4 triomes | Passage, no triomes | Passage, 4 triomes | 8 Fetches, 5 Basics | 8 Fetches, 6 Basics |");
println!("|------:|-------------------------:|-------------------------:|-------------------------:|-------------------------:|-------------------------:|--------------------------|-------------------------:|-------------------------:|");
for land_count in 20..=30 {
run_sim(60, land_count, 4);
}
}
}
fn main() {
mono_color::run_all_sims();
dual_color::run_all_sims();
three_colors::run_all_sims();
}
extern crate rand;
use rand::prelude::*;
use std::ops::RangeInclusive;
struct Deck {
total_cards: u32,
land_cards: u32,
}
#[derive(Copy, Clone, PartialEq, Eq)]
enum CardType {
Land,
Spell
}
impl Deck {
fn new(deck_size: u32, land_count: u32) -> Self {
Deck {
total_cards: deck_size,
land_cards: land_count,
}
}
fn draw_card(&mut self, rng: &mut ThreadRng) -> CardType {
let random_int_between_one_and_total_cards = rng.gen_range(0, self.total_cards) + 1;
if random_int_between_one_and_total_cards <= self.land_cards {
self.total_cards -= 1;
self.land_cards -= 1;
CardType::Land
} else {
self.total_cards -= 1;
CardType::Spell
}
}
}
fn run_sim(deck_size: u32, land_count: u32, turns_allowed: u32, mull_strategy: &[RangeInclusive<u32>]) {
const NUM_ITERATIONS: u32 = 10_000_000;
assert!(turns_allowed <= 7);
let mut land_drops_hit = [vec![0; turns_allowed as usize].into_boxed_slice(), vec![0; turns_allowed as usize].into_boxed_slice()];
let mut count_mana_flooded = 0;
let mut sum_of_all_starting_hand_sizes = 0;
let mut rng = rand::thread_rng();
let first_turn_to_draw_max = if deck_size == 99 { 1 } else { 2 };
for first_turn_to_draw in 1..=first_turn_to_draw_max {
for _ in 0..NUM_ITERATIONS {
let mut deck = Deck::new(deck_size, land_count);
let mut starting_hand_size = 7;
let mut lands_in_hand = 0;
let mut lands_in_play = 0;
let mut free_mulligan = deck_size == 99;
loop {
for _ in 0..7 {
let card_type = deck.draw_card(&mut rng);
if card_type == CardType::Land {
lands_in_hand += 1;
}
}
for _ in starting_hand_size..7 {
if lands_in_hand > starting_hand_size / 2 {
lands_in_hand -= 1;
}
}
if starting_hand_size == 4 || mull_strategy[7 - starting_hand_size as usize].contains(&lands_in_hand) {
break;
}
if free_mulligan {
free_mulligan = false;
continue;
}
starting_hand_size -= 1;
}
assert!(starting_hand_size >= 4);
sum_of_all_starting_hand_sizes += starting_hand_size;
for turn in 1..=7 {
if turn >= first_turn_to_draw {
let card_type = deck.draw_card(&mut rng);
if card_type == CardType::Land {
lands_in_hand += 1;
}
}
if lands_in_hand > 0 {
lands_in_hand -= 1;
lands_in_play += 1;
}
if lands_in_play == turn && lands_in_play <= turns_allowed {
land_drops_hit[first_turn_to_draw as usize - 1][turn as usize - 1] += 1;
}
}
if first_turn_to_draw == 1 && lands_in_play + lands_in_hand >= 8 {
count_mana_flooded += 1;
}
}
}
print!("| {:5} | ", land_count);
for i in 2..turns_allowed as usize {
if deck_size == 99 {
let freq = land_drops_hit[0][i] as f64 / NUM_ITERATIONS as f64 * 100.0;
print!("{:15.2}% | ", freq);
} else {
let freq_on_the_play = land_drops_hit[0][i] as f64 / NUM_ITERATIONS as f64 * 100.0;
let freq_on_the_draw = land_drops_hit[1][i] as f64 / NUM_ITERATIONS as f64 * 100.0;
print!(" {:6.2}% / {:6.2}% | ", freq_on_the_play, freq_on_the_draw);
}
}
let total_sims_run = if deck_size == 99 { NUM_ITERATIONS } else { 2 * NUM_ITERATIONS };
let avg_starting_hand_size = sum_of_all_starting_hand_sizes as f64 / total_sims_run as f64;
let freq_mana_flooded_on_the_draw = count_mana_flooded as f64 / NUM_ITERATIONS as f64 * 100.0;
println!("{:.3} cards | {:5.1}% |", avg_starting_hand_size, freq_mana_flooded_on_the_draw);
}
fn main() {
let mull_strategy = &[2..=5, 2..=4, 1..=4, 0..=4];
println!("## Limited decks (40 cards):");
println!("| Lands | P(3 lands on T3) | P(4 lands on T4) | P(5 lands on T5) | mean hand | P(8+ lands on T7) |");
println!("|------:|-------------------:|-------------------:|-------------------:|:------------|------------------:|");
for land_count in 10..=20 {
run_sim(40, land_count, 5, mull_strategy);
}
println!();
println!("## Constructed decks (60 cards):");
println!("| Lands | P(3 lands on T3) | P(4 lands on T4) | P(5 lands on T5) | mean hand | P(8+ lands on T7) |");
println!("|------:|-------------------:|-------------------:|-------------------:|:------------|------------------:|");
for land_count in 15..=30 {
run_sim(60, land_count, 5, mull_strategy);
}
println!();
println!("## Yorion decks (80 cards):");
println!("| Lands | P(3 lands on T3) | P(4 lands on T4) | P(5 lands on T5) | mean hand | P(8+ lands on T7) |");
println!("|------:|-------------------:|-------------------:|-------------------:|:------------|------------------:|");
for land_count in 20..=40 {
run_sim(80, land_count, 5, mull_strategy);
}
println!();
println!("## Commander decks (99 cards):");
println!("| Lands | P(3 lands on T3) | P(4 lands on T4) | P(5 lands on T5) | P(6 lands on T6) | mean hand | P(8+ lands on T7) |");
println!("|------:|-----------------:|-----------------:|-----------------:|-----------------:|:------------|------------------:|");
for land_count in 24..=49 {
run_sim(99, land_count, 6, mull_strategy);
}
}
use rand::prelude::*;
#[derive(Copy, Clone, PartialEq, Eq)]
enum CardType {
NonLand,
Land,
}
#[derive(Copy, Clone)]
struct Deck {
total_cards: u32,
total_lands: u32,
}
#[derive(Copy, Clone)]
struct OpeningHand {
lands_in_hand: u32,
}
// A mulligan strategy decides whether or not to keep a hand, and if so, which cards to bottom
type MullStrat = fn(OpeningHand, u32) -> Option<OpeningHand>;
impl Deck {
fn new(total_cards: u32, total_lands: u32) -> Self {
Deck { total_cards, total_lands }
}
fn draw_card(&mut self, rng: &mut ThreadRng) -> CardType {
let int_between_one_and_deck_size = rng.gen_range(0, self.total_cards) + 1;
let land_cutoff = self.total_lands;
if int_between_one_and_deck_size <= land_cutoff {
self.total_cards -= 1;
self.total_lands -= 1;
CardType::Land
} else if int_between_one_and_deck_size > land_cutoff {
self.total_cards -= 1;
CardType::NonLand
} else {
unreachable!()
}
}
fn draw_opening_hand(&mut self, rng: &mut ThreadRng) -> OpeningHand {
let mut lands_in_hand = 0;
for _ in 0..7 {
match self.draw_card(rng) {
CardType::Land => {
lands_in_hand += 1;
},
CardType::NonLand => {}
}
}
OpeningHand {
lands_in_hand,
}
}
}
fn run_sim(deck_size: u32, land_count: u32, mull_strategy: MullStrat, lands_needed_to_function: u32) {
assert!(deck_size >= land_count);
const NUM_ITERATIONS: u32 = 5_000_000;
let mut rng = thread_rng();
let mut sum_of_all_starting_hand_sizes = 0;
let mut count_cards_kept = [0, 0, 0, 0];
// we want to get some measure of flooding;
// getting one more land on curve than we _need_ to function is nice,
// getting another two in close proximity is our definition of flood
let max_cmc = lands_needed_to_function + 3;
let mut freq_curve_out = [
vec![0u32; max_cmc as usize].into_boxed_slice(),
vec![0u32; max_cmc as usize].into_boxed_slice()
];
let mut freq_flooded = 0;
let first_turn_to_draw_max = if deck_size == 99 { 1 } else { 2 };
for first_turn_to_draw in 1..=first_turn_to_draw_max {
for _ in 0..NUM_ITERATIONS {
let mut deck = Deck::new(deck_size, land_count);
let mut starting_hand_size = 7;
let mut free_mulligan = deck_size == 99;
let OpeningHand { mut lands_in_hand } = loop {
let opened = deck.draw_opening_hand(&mut rng);
if let Some(kept_hand) = mull_strategy(opened, starting_hand_size) {
break kept_hand;
}
if free_mulligan {
free_mulligan = false;
continue;
}
starting_hand_size -= 1;
};
sum_of_all_starting_hand_sizes += starting_hand_size;
count_cards_kept[7 - starting_hand_size as usize] += 1;
let mut lands_in_play = 0;
for turn in 1..=max_cmc {
if turn >= first_turn_to_draw {
if deck.draw_card(&mut rng) == CardType::Land {
lands_in_hand += 1;
}
}
if lands_in_hand >= 1 {
lands_in_play += 1;
lands_in_hand -= 1;
assert!(lands_in_play <= turn)
}
if lands_in_play == turn {
freq_curve_out[first_turn_to_draw as usize - 1][turn as usize - 1] += 1;
}
}
if first_turn_to_draw == 1 && lands_in_play + lands_in_hand > max_cmc {
freq_flooded += 1;
}
}
}
let total_iterations = if deck_size == 99 { NUM_ITERATIONS } else { 2 * NUM_ITERATIONS };
print!("| {:5} | ", land_count);
for count in &count_cards_kept {
let percentage = *count as f64 / total_iterations as f64 * 100.0;
print!("{:7.2}% | ", percentage);
}
for turn in lands_needed_to_function..=(lands_needed_to_function + 1) {
let count_curved_out_on_the_draw = freq_curve_out[0][turn as usize - 1];
let count_curved_out_on_the_play = freq_curve_out[1][turn as usize - 1];
let percentage_on_the_draw = count_curved_out_on_the_draw as f64 / NUM_ITERATIONS as f64 * 100.0;
let percentage_on_the_play = count_curved_out_on_the_play as f64 / NUM_ITERATIONS as f64 * 100.0;
if deck_size == 99 {
print!(" {:6.2}% | ", percentage_on_the_draw);
} else {
print!("{:6.2}% / {:6.2}% | ", percentage_on_the_draw, percentage_on_the_play);
}
}
let mean_starting_hand_size = sum_of_all_starting_hand_sizes as f64 / total_iterations as f64;
let percentage_mana_flooded = freq_flooded as f64 / NUM_ITERATIONS as f64 * 100.0;
println!("{:4.3} cards | {:16.2}% |", mean_starting_hand_size, percentage_mana_flooded);
}
fn low_curve_mulligan_strategy(opened: OpeningHand, starting_hand_size: u32) -> Option<OpeningHand> {
let lands_opened = opened.lands_in_hand;
match starting_hand_size {
7 => {
if lands_opened < 2 || lands_opened > 3 {
None
} else {
Some(opened)
}
},
6 => {
if lands_opened < 2 || lands_opened > 4 {
None
} else if lands_opened < 4 {
Some(opened) // bottom a spell
} else if lands_opened == 4 {
Some(OpeningHand { lands_in_hand: 3 }) // bottom a land
} else {
unreachable!()
}
},
5 => {
if lands_opened == 0 || lands_opened > 5 {
None
} else if lands_opened == 5 {
Some(OpeningHand { lands_in_hand: 3 })
} else if lands_opened >= 3 { // opened 3, 4 or 5 lands, keep 2
Some(OpeningHand { lands_in_hand: 2 })
} else { // opened 1 or 2 lands, bottom spells
Some(OpeningHand { lands_in_hand: lands_opened })
}
},
4 => {
if lands_opened >= 5 {
Some(OpeningHand { lands_in_hand: lands_opened - 3 })
} else if lands_opened >= 2 {
Some(OpeningHand { lands_in_hand: 2 })
} else {
Some(opened)
}
},
_ => unreachable!()
}
}
fn midrange_mulligan_strategy(opened: OpeningHand, starting_hand_size: u32) -> Option<OpeningHand> {
let lands_opened = opened.lands_in_hand;
match starting_hand_size {
7 => {
if lands_opened < 2 || lands_opened > 4 {
None
} else {
Some(opened)
}
},
6 => {
if lands_opened < 2 || lands_opened > 5 {
None
} else if lands_opened < 4 {
Some(opened) // bottom a spell
} else if lands_opened <= 5 {
Some(OpeningHand { lands_in_hand: lands_opened - 1 }) // bottom a land
} else {
unreachable!()
}
},
5 => {
if lands_opened == 0 || lands_opened > 6 {
None
} else if lands_opened <= 3 {
Some(opened)
} else if lands_opened == 4 {
Some(OpeningHand { lands_in_hand: 3 })
} else { // opened 5 or 6 lands, bottom 2 of them
Some(OpeningHand { lands_in_hand: 4 })
}
},
4 => {
if lands_opened >= 5 {
Some(OpeningHand { lands_in_hand: lands_opened - 3 })
} else if lands_opened <= 2 {
Some(opened) // bottom spells
} else { // opened 3 or 4 lands, go down to 2
Some(OpeningHand { lands_in_hand: 2 })
}
},
_ => unreachable!()
}
}
fn high_curve_mulligan_strategy(opened: OpeningHand, starting_hand_size: u32) -> Option<OpeningHand> {
let lands_opened = opened.lands_in_hand;
match starting_hand_size {
7 => {
if lands_opened < 3 || lands_opened > 4 {
None
} else {
Some(opened)
}
},
6 => {
if lands_opened < 2 || lands_opened > 5 {
None
} else if lands_opened <= 4 {
Some(opened) // bottom a spell
} else {
Some(OpeningHand { lands_in_hand: lands_opened - 1 })
}
},
5 => {
if lands_opened <= 1 || lands_opened == 7 {
None
} else if lands_opened < 4 {
Some(OpeningHand { lands_in_hand: lands_opened })
} else {
Some(OpeningHand { lands_in_hand: 4 })
}
},
4 => {
if lands_opened >= 6 {
Some(OpeningHand { lands_in_hand: lands_opened - 3 })
} else if lands_opened >= 3 {
Some(OpeningHand { lands_in_hand: 2 })
} else {
Some(opened)
}
},
_ => unreachable!()
}
}
fn print_table_header(lands_needed_to_function: u32) {
print!("| Lands | ");
print!("P(S = 7) | ");
print!("P(S = 6) | ");
print!("P(S = 5) | ");
print!("P(S = 4) | ");
print!("P({0} lands on T{0}) | ", lands_needed_to_function);
print!("P({0} lands on T{0}) | ", lands_needed_to_function + 1);
print!("mean hand | ");
println!("P({}+ lands on T{}) |", lands_needed_to_function + 4, lands_needed_to_function + 3);
print!("|------:|");
print!("---------:|");
print!("---------:|");
print!("---------:|");
print!("---------:|");
print!("------------------:|");
print!("------------------:|");
print!(":------------|");
println!("------------------:|");
}
fn main() {
println!("## Low-curve mulligan strategy");
println!("### Limited decks (40 cards):");
print_table_header(2);
for land_count in 10..=20 {
run_sim(40, land_count, low_curve_mulligan_strategy, 2);
}
println!("### Constructed decks (60 cards):");
print_table_header(2);
for land_count in 15..=30 {
run_sim(60, land_count, low_curve_mulligan_strategy, 2);
}
println!("### Constructed decks (80 cards):");
print_table_header(2);
for land_count in 20..=40 {
run_sim(80, land_count, low_curve_mulligan_strategy, 2);
}
println!("### Commander decks (99 cards):");
print_table_header(2);
for land_count in 24..=49 {
run_sim(99, land_count, low_curve_mulligan_strategy, 2);
}
println!();
println!("## Midrange mulligan strategy");
println!("### Limited decks (40 cards):");
print_table_header(3);
for land_count in 10..=20 {
run_sim(40, land_count, midrange_mulligan_strategy, 3);
}
println!("### Constructed decks (60 cards):");
print_table_header(3);
for land_count in 15..=30 {
run_sim(60, land_count, midrange_mulligan_strategy, 3);
}
println!("### Yorion decks (80 cards):");
print_table_header(3);
for land_count in 20..=40 {
run_sim(80, land_count, midrange_mulligan_strategy, 3);
}
println!("### Commander decks (99 cards):");
print_table_header(3);
for land_count in 24..=49 {
run_sim(99, land_count, midrange_mulligan_strategy, 3);
}
println!();
println!("## High-curve mulligan strategy");
println!("### Limited decks (40 cards):");
print_table_header(4);
for land_count in 13..=24 {
run_sim(40, land_count, high_curve_mulligan_strategy, 4);
}
println!("### Constructed decks (60 cards):");
print_table_header(4);
for land_count in 20..=36 {
run_sim(60, land_count, high_curve_mulligan_strategy, 4);
}
println!("### Yorion decks (80 cards):");
print_table_header(4);
for land_count in 25..=48 {
run_sim(80, land_count, high_curve_mulligan_strategy, 4);
}
println!("### Commander decks (99 cards):");
print_table_header(4);
for land_count in 33..=50 {
run_sim(99, land_count, high_curve_mulligan_strategy, 4);
}
}

Low-curve mulligan strategy

Limited decks (40 cards):

Lands P(S = 7) P(S = 6) P(S = 5) P(S = 4) P(2 lands on T2) P(3 lands on T3) mean hand P(6+ lands on T5)
10 52.08% 29.37% 17.27% 1.28% 97.83% / 96.75% 70.85% / 60.56% 6.323 cards 2.45%
11 56.07% 28.97% 14.08% 0.88% 98.52% / 97.73% 75.87% / 66.03% 6.402 cards 4.07%
12 58.97% 28.47% 11.90% 0.65% 98.94% / 98.35% 79.92% / 70.56% 6.458 cards 6.18%
13 60.68% 28.34% 10.50% 0.49% 99.24% / 98.75% 83.27% / 74.38% 6.492 cards 8.72%
14 61.30% 28.69% 9.62% 0.40% 99.43% / 99.04% 86.11% / 77.86% 6.509 cards 11.62%
15 60.78% 29.77% 9.11% 0.34% 99.56% / 99.23% 88.52% / 80.86% 6.510 cards 14.95%
16 59.29% 31.52% 8.87% 0.33% 99.64% / 99.36% 90.60% / 83.55% 6.498 cards 18.69%
17 56.82% 33.91% 8.94% 0.34% 99.68% / 99.44% 92.36% / 85.97% 6.472 cards 22.91%
18 53.62% 36.63% 9.38% 0.37% 99.71% / 99.47% 93.73% / 87.99% 6.435 cards 27.49%
19 49.76% 39.49% 10.32% 0.44% 99.74% / 99.49% 94.82% / 89.66% 6.386 cards 32.43%
20 45.42% 42.15% 11.89% 0.53% 99.76% / 99.51% 95.68% / 91.01% 6.325 cards 37.48%

Constructed decks (60 cards):

Lands P(S = 7) P(S = 6) P(S = 5) P(S = 4) P(2 lands on T2) P(3 lands on T3) mean hand P(6+ lands on T5)
15 50.75% 28.81% 18.64% 1.81% 96.98% / 95.70% 69.22% / 59.21% 6.285 cards 3.01%
16 53.42% 28.86% 16.36% 1.36% 97.74% / 96.70% 72.92% / 63.04% 6.343 cards 4.09%
17 55.59% 28.85% 14.52% 1.03% 98.29% / 97.44% 76.05% / 66.48% 6.390 cards 5.34%
18 57.35% 28.81% 13.03% 0.81% 98.66% / 97.96% 78.86% / 69.54% 6.427 cards 6.78%
19 58.59% 28.92% 11.83% 0.66% 98.95% / 98.35% 81.33% / 72.33% 6.454 cards 8.43%
20 59.36% 29.17% 10.92% 0.55% 99.16% / 98.65% 83.46% / 74.85% 6.473 cards 10.26%
21 59.61% 29.64% 10.27% 0.48% 99.31% / 98.88% 85.40% / 77.17% 6.484 cards 12.28%
22 59.46% 30.33% 9.79% 0.42% 99.44% / 99.04% 87.12% / 79.29% 6.488 cards 14.46%
23 58.81% 31.27% 9.52% 0.39% 99.51% / 99.17% 88.68% / 81.25% 6.485 cards 16.83%
24 57.81% 32.40% 9.41% 0.38% 99.58% / 99.26% 90.03% / 83.01% 6.476 cards 19.37%
25 56.40% 33.74% 9.48% 0.38% 99.63% / 99.34% 91.26% / 84.64% 6.462 cards 22.11%
26 54.65% 35.21% 9.75% 0.39% 99.67% / 99.40% 92.32% / 86.10% 6.441 cards 24.99%
27 52.55% 36.80% 10.23% 0.42% 99.70% / 99.44% 93.27% / 87.44% 6.415 cards 27.97%
28 50.23% 38.36% 10.93% 0.47% 99.73% / 99.47% 94.06% / 88.60% 6.383 cards 31.07%
29 47.65% 39.92% 11.89% 0.55% 99.75% / 99.50% 94.78% / 89.64% 6.347 cards 34.25%
30 44.87% 41.34% 13.14% 0.66% 99.77% / 99.53% 95.38% / 90.55% 6.304 cards 37.55%

Constructed decks (80 cards):

Lands P(S = 7) P(S = 6) P(S = 5) P(S = 4) P(2 lands on T2) P(3 lands on T3) mean hand P(6+ lands on T5)
20 50.16% 28.55% 19.16% 2.13% 96.49% / 95.11% 68.47% / 58.59% 6.267 cards 3.28%
21 52.14% 28.70% 17.47% 1.70% 97.18% / 95.99% 71.32% / 61.45% 6.313 cards 4.07%
22 53.89% 28.78% 15.94% 1.38% 97.72% / 96.72% 73.90% / 64.23% 6.352 cards 4.98%
23 55.39% 28.86% 14.63% 1.13% 98.14% / 97.27% 76.20% / 66.72% 6.385 cards 5.98%
24 56.58% 28.96% 13.53% 0.94% 98.48% / 97.72% 78.30% / 69.09% 6.412 cards 7.09%
25 57.54% 29.11% 12.57% 0.78% 98.74% / 98.06% 80.22% / 71.20% 6.434 cards 8.32%
26 58.25% 29.30% 11.78% 0.67% 98.94% / 98.35% 81.95% / 73.19% 6.451 cards 9.62%
27 58.66% 29.61% 11.14% 0.59% 99.11% / 98.58% 83.58% / 75.08% 6.463 cards 11.05%
28 58.85% 30.01% 10.62% 0.52% 99.24% / 98.77% 85.02% / 76.84% 6.472 cards 12.61%
29 58.75% 30.52% 10.26% 0.47% 99.34% / 98.92% 86.39% / 78.45% 6.476 cards 14.22%
30 58.44% 31.14% 9.98% 0.44% 99.42% / 99.05% 87.62% / 79.98% 6.476 cards 15.93%
31 57.88% 31.87% 9.83% 0.41% 99.49% / 99.14% 88.72% / 81.42% 6.472 cards 17.78%
32 57.10% 32.74% 9.75% 0.41% 99.55% / 99.22% 89.77% / 82.73% 6.465 cards 19.69%
33 56.12% 33.65% 9.82% 0.41% 99.60% / 99.29% 90.70% / 83.94% 6.455 cards 21.68%
34 54.96% 34.64% 9.98% 0.41% 99.63% / 99.34% 91.53% / 85.09% 6.442 cards 23.76%
35 53.56% 35.73% 10.28% 0.43% 99.66% / 99.39% 92.31% / 86.17% 6.424 cards 25.95%
36 52.08% 36.78% 10.68% 0.47% 99.70% / 99.42% 93.03% / 87.16% 6.405 cards 28.16%
37 50.36% 37.92% 11.22% 0.51% 99.71% / 99.46% 93.67% / 88.06% 6.381 cards 30.46%
38 48.53% 39.01% 11.90% 0.57% 99.73% / 99.48% 94.24% / 88.88% 6.355 cards 32.76%
39 46.62% 40.02% 12.71% 0.65% 99.75% / 99.51% 94.74% / 89.66% 6.326 cards 35.18%
40 44.58% 40.98% 13.69% 0.76% 99.77% / 99.53% 95.22% / 90.33% 6.294 cards 37.52%

Commander decks (99 cards):

Lands P(S = 7) P(S = 6) P(S = 5) P(S = 4) P(2 lands on T2) P(3 lands on T3) mean hand P(6+ lands on T5)
24 74.09% 14.61% 10.08% 1.22% 98.00% 69.49% 6.616 cards 3.34%
25 75.73% 14.21% 9.07% 0.99% 98.38% 71.57% 6.647 cards 3.96%
26 77.17% 13.82% 8.19% 0.82% 98.65% 73.52% 6.673 cards 4.64%
27 78.41% 13.47% 7.45% 0.67% 98.89% 75.32% 6.696 cards 5.36%
28 79.46% 13.18% 6.79% 0.56% 99.08% 76.97% 6.715 cards 6.14%
29 80.37% 12.90% 6.25% 0.47% 99.22% 78.52% 6.732 cards 7.02%
30 81.07% 12.72% 5.80% 0.41% 99.34% 79.98% 6.744 cards 7.91%
31 81.66% 12.57% 5.43% 0.35% 99.44% 81.33% 6.755 cards 8.92%
32 82.11% 12.48% 5.11% 0.31% 99.52% 82.59% 6.764 cards 9.96%
33 82.43% 12.45% 4.84% 0.28% 99.58% 83.77% 6.770 cards 11.09%
34 82.60% 12.50% 4.65% 0.25% 99.63% 84.90% 6.775 cards 12.30%
35 82.64% 12.64% 4.49% 0.23% 99.67% 85.92% 6.777 cards 13.50%
36 82.57% 12.83% 4.39% 0.21% 99.70% 86.93% 6.778 cards 14.80%
37 82.34% 13.13% 4.33% 0.20% 99.73% 87.81% 6.776 cards 16.17%
38 82.09% 13.41% 4.30% 0.19% 99.76% 88.67% 6.774 cards 17.59%
39 81.67% 13.83% 4.30% 0.19% 99.78% 89.50% 6.770 cards 19.11%
40 81.09% 14.35% 4.37% 0.19% 99.79% 90.24% 6.763 cards 20.63%
41 80.47% 14.88% 4.47% 0.19% 99.81% 90.96% 6.756 cards 22.24%
42 79.65% 15.55% 4.61% 0.20% 99.82% 91.63% 6.746 cards 23.90%
43 78.77% 16.23% 4.79% 0.21% 99.83% 92.27% 6.736 cards 25.64%
44 77.74% 17.02% 5.02% 0.22% 99.83% 92.85% 6.723 cards 27.40%
45 76.58% 17.87% 5.32% 0.24% 99.84% 93.37% 6.708 cards 29.22%
46 75.30% 18.77% 5.66% 0.26% 99.85% 93.90% 6.691 cards 31.07%
47 73.85% 19.77% 6.09% 0.29% 99.85% 94.33% 6.672 cards 32.95%
48 72.31% 20.81% 6.55% 0.33% 99.86% 94.79% 6.651 cards 34.79%
49 70.69% 21.82% 7.11% 0.38% 99.86% 95.18% 6.628 cards 36.79%

Midrange mulligan strategy

Limited decks (40 cards):

Lands P(S = 7) P(S = 6) P(S = 5) P(S = 4) P(3 lands on T3) P(4 lands on T4) mean hand P(7+ lands on T6)
10 56.60% 28.11% 14.57% 0.72% 76.04% / 67.11% 44.99% / 34.26% 6.406 cards 0.96%
11 62.55% 26.55% 10.53% 0.38% 80.85% / 72.56% 52.68% / 41.34% 6.513 cards 1.99%
12 67.65% 24.55% 7.58% 0.22% 84.62% / 76.98% 59.63% / 47.95% 6.596 cards 3.58%
13 71.93% 22.37% 5.56% 0.15% 87.63% / 80.66% 65.69% / 54.10% 6.661 cards 5.81%
14 75.26% 20.37% 4.27% 0.10% 90.09% / 83.80% 71.19% / 59.77% 6.708 cards 8.72%
15 77.63% 18.80% 3.49% 0.07% 92.16% / 86.56% 75.98% / 65.08% 6.740 cards 12.29%
16 79.06% 17.85% 3.05% 0.05% 93.86% / 88.91% 80.28% / 69.93% 6.759 cards 16.53%
17 79.44% 17.73% 2.80% 0.04% 95.26% / 90.95% 84.00% / 74.39% 6.766 cards 21.33%
18 78.92% 18.40% 2.65% 0.03% 96.39% / 92.73% 87.20% / 78.39% 6.762 cards 26.69%
19 77.42% 19.98% 2.57% 0.03% 97.32% / 94.23% 89.95% / 82.00% 6.748 cards 32.45%
20 75.06% 22.37% 2.55% 0.03% 98.02% / 95.50% 92.20% / 85.21% 6.725 cards 38.50%

Constructed decks (60 cards):

Lands P(S = 7) P(S = 6) P(S = 5) P(S = 4) P(3 lands on T3) P(4 lands on T4) mean hand P(7+ lands on T6)
15 55.76% 27.05% 15.90% 1.30% 74.18% / 65.51% 44.19% / 34.01% 6.373 cards 1.50%
16 59.63% 26.34% 13.17% 0.87% 77.80% / 69.39% 49.30% / 38.60% 6.447 cards 2.26%
17 63.22% 25.39% 10.81% 0.58% 80.86% / 72.80% 54.08% / 43.03% 6.513 cards 3.23%
18 66.44% 24.31% 8.86% 0.39% 83.44% / 75.88% 58.55% / 47.36% 6.568 cards 4.46%
19 69.30% 23.12% 7.31% 0.27% 85.71% / 78.54% 62.72% / 51.47% 6.614 cards 5.94%
20 71.73% 22.02% 6.06% 0.19% 87.71% / 80.95% 66.61% / 55.41% 6.653 cards 7.70%
21 73.78% 20.98% 5.10% 0.14% 89.39% / 83.13% 70.21% / 59.17% 6.684 cards 9.73%
22 75.43% 20.09% 4.37% 0.11% 90.86% / 85.05% 73.50% / 62.70% 6.708 cards 12.05%
23 76.64% 19.45% 3.82% 0.08% 92.21% / 86.77% 76.60% / 66.05% 6.727 cards 14.61%
24 77.49% 19.03% 3.41% 0.07% 93.35% / 88.37% 79.41% / 69.24% 6.739 cards 17.48%
25 77.83% 18.99% 3.13% 0.06% 94.37% / 89.79% 81.98% / 72.22% 6.746 cards 20.58%
26 77.78% 19.24% 2.94% 0.05% 95.24% / 91.11% 84.29% / 75.04% 6.747 cards 23.87%
27 77.39% 19.76% 2.81% 0.04% 96.01% / 92.29% 86.39% / 77.67% 6.745 cards 27.39%
28 76.53% 20.69% 2.74% 0.04% 96.67% / 93.32% 88.31% / 80.14% 6.737 cards 31.05%
29 75.29% 21.94% 2.74% 0.03% 97.25% / 94.28% 90.01% / 82.44% 6.725 cards 34.89%
30 73.69% 23.47% 2.80% 0.04% 97.75% / 95.13% 91.55% / 84.54% 6.708 cards 38.82%

Yorion decks (80 cards):

Lands P(S = 7) P(S = 6) P(S = 5) P(S = 4) P(3 lands on T3) P(4 lands on T4) mean hand P(7+ lands on T6)
20 55.40% 26.53% 16.44% 1.63% 73.36% / 64.80% 43.90% / 33.92% 6.357 cards 1.79%
21 58.29% 26.11% 14.38% 1.22% 76.16% / 67.80% 47.61% / 37.33% 6.415 cards 2.36%
22 61.01% 25.56% 12.51% 0.91% 78.62% / 70.52% 51.27% / 40.64% 6.467 cards 3.07%
23 63.54% 24.89% 10.90% 0.68% 80.85% / 73.03% 54.73% / 43.88% 6.513 cards 3.90%
24 65.86% 24.20% 9.43% 0.51% 82.91% / 75.31% 58.10% / 47.10% 6.554 cards 4.87%
25 67.98% 23.42% 8.21% 0.39% 84.67% / 77.38% 61.21% / 50.16% 6.590 cards 5.99%
26 69.91% 22.64% 7.16% 0.29% 86.27% / 79.35% 64.20% / 53.15% 6.622 cards 7.24%
27 71.60% 21.92% 6.25% 0.23% 87.70% / 81.12% 67.02% / 56.05% 6.649 cards 8.68%
28 73.09% 21.23% 5.50% 0.18% 88.99% / 82.75% 69.73% / 58.83% 6.672 cards 10.19%
29 74.35% 20.62% 4.89% 0.14% 90.20% / 84.23% 72.26% / 61.52% 6.692 cards 11.95%
30 75.37% 20.15% 4.37% 0.11% 91.25% / 85.63% 74.60% / 64.05% 6.708 cards 13.74%
31 76.15% 19.79% 3.97% 0.09% 92.19% / 86.88% 76.86% / 66.53% 6.720 cards 15.78%
32 76.69% 19.59% 3.65% 0.08% 93.08% / 88.10% 78.96% / 68.95% 6.729 cards 17.91%
33 77.03% 19.52% 3.39% 0.07% 93.87% / 89.19% 80.89% / 71.16% 6.735 cards 20.15%
34 77.12% 19.63% 3.19% 0.06% 94.59% / 90.23% 82.72% / 73.33% 6.738 cards 22.54%
35 76.96% 19.94% 3.04% 0.05% 95.23% / 91.17% 84.43% / 75.38% 6.738 cards 25.07%
36 76.60% 20.42% 2.94% 0.04% 95.81% / 92.05% 86.00% / 77.33% 6.736 cards 27.71%
37 76.02% 21.06% 2.88% 0.04% 96.34% / 92.86% 87.49% / 79.24% 6.731 cards 30.42%
38 75.23% 21.87% 2.86% 0.04% 96.81% / 93.62% 88.80% / 80.99% 6.723 cards 33.18%
39 74.22% 22.85% 2.89% 0.04% 97.22% / 94.31% 90.07% / 82.64% 6.713 cards 36.08%
40 73.01% 24.00% 2.96% 0.04% 97.60% / 94.95% 91.20% / 84.23% 6.700 cards 39.00%

Commander decks (99 cards):

Lands P(S = 7) P(S = 6) P(S = 5) P(S = 4) P(3 lands on T3) P(4 lands on T4) mean hand P(7+ lands on T6)
24 79.34% 12.27% 7.60% 0.80% 73.68% 44.01% 6.701 cards 1.94%
25 81.44% 11.49% 6.46% 0.61% 75.75% 46.90% 6.738 cards 2.42%
26 83.34% 10.71% 5.50% 0.45% 77.65% 49.68% 6.769 cards 2.97%
27 85.08% 9.95% 4.63% 0.34% 79.41% 52.44% 6.798 cards 3.60%
28 86.55% 9.27% 3.92% 0.25% 81.00% 55.03% 6.821 cards 4.31%
29 87.93% 8.55% 3.33% 0.19% 82.51% 57.58% 6.842 cards 5.09%
30 89.12% 7.91% 2.83% 0.15% 83.86% 60.00% 6.860 cards 5.95%
31 90.13% 7.33% 2.42% 0.11% 85.12% 62.32% 6.875 cards 6.92%
32 91.01% 6.82% 2.08% 0.09% 86.31% 64.62% 6.887 cards 7.99%
33 91.77% 6.35% 1.80% 0.07% 87.41% 66.79% 6.898 cards 9.13%
34 92.44% 5.92% 1.58% 0.06% 88.47% 68.93% 6.907 cards 10.37%
35 92.97% 5.60% 1.38% 0.05% 89.38% 70.89% 6.915 cards 11.70%
36 93.41% 5.32% 1.23% 0.04% 90.28% 72.81% 6.921 cards 13.11%
37 93.73% 5.12% 1.11% 0.03% 91.10% 74.67% 6.926 cards 14.65%
38 94.01% 4.95% 1.01% 0.03% 91.86% 76.42% 6.929 cards 16.22%
39 94.22% 4.82% 0.94% 0.02% 92.57% 78.11% 6.932 cards 17.95%
40 94.37% 4.74% 0.87% 0.02% 93.23% 79.72% 6.935 cards 19.72%
41 94.46% 4.70% 0.83% 0.02% 93.85% 81.23% 6.936 cards 21.59%
42 94.49% 4.71% 0.79% 0.02% 94.43% 82.68% 6.937 cards 23.55%
43 94.46% 4.78% 0.76% 0.01% 94.94% 84.03% 6.937 cards 25.55%
44 94.37% 4.88% 0.74% 0.01% 95.42% 85.31% 6.936 cards 27.64%
45 94.24% 5.02% 0.74% 0.01% 95.86% 86.53% 6.935 cards 29.83%
46 94.01% 5.24% 0.74% 0.01% 96.29% 87.65% 6.933 cards 32.01%
47 93.74% 5.49% 0.76% 0.01% 96.66% 88.72% 6.930 cards 34.32%
48 93.39% 5.82% 0.77% 0.01% 96.99% 89.72% 6.926 cards 36.64%
49 92.98% 6.21% 0.80% 0.01% 97.30% 90.66% 6.922 cards 38.98%

High-curve mulligan strategy

Limited decks (40 cards):

Lands P(S = 7) P(S = 6) P(S = 5) P(S = 4) P(4 lands on T4) P(5 lands on T5) mean hand P(8+ lands on T7)
13 38.13% 48.24% 11.70% 1.94% 76.83% / 67.18% 53.10% / 41.15% 6.226 cards 3.22%
14 43.17% 46.54% 9.08% 1.20% 81.93% / 73.13% 60.76% / 48.47% 6.317 cards 5.50%
15 47.72% 44.39% 7.10% 0.80% 85.97% / 78.04% 67.51% / 55.28% 6.390 cards 8.51%
16 51.69% 42.09% 5.65% 0.57% 89.13% / 82.12% 73.28% / 61.49% 6.449 cards 12.28%
17 54.88% 39.94% 4.74% 0.43% 91.53% / 85.43% 78.21% / 67.01% 6.493 cards 16.70%
18 57.29% 38.19% 4.18% 0.34% 93.46% / 88.14% 82.40% / 71.95% 6.524 cards 21.71%
19 58.76% 37.05% 3.91% 0.28% 94.92% / 90.33% 85.89% / 76.34% 6.543 cards 27.27%
20 59.25% 36.67% 3.83% 0.25% 96.09% / 92.17% 88.79% / 80.20% 6.549 cards 33.21%
21 58.77% 37.04% 3.96% 0.23% 97.02% / 93.69% 91.26% / 83.63% 6.543 cards 39.50%
22 57.29% 38.18% 4.30% 0.23% 97.79% / 94.97% 93.35% / 86.67% 6.525 cards 46.07%
23 54.89% 39.94% 4.93% 0.24% 98.40% / 96.10% 95.07% / 89.38% 6.495 cards 52.81%
24 51.68% 42.08% 5.98% 0.26% 98.90% / 97.06% 96.50% / 91.79% 6.452 cards 59.66%
25 47.73% 44.38% 7.62% 0.27% 99.28% / 97.88% 97.63% / 93.86% 6.396 cards 66.47%
26 43.14% 46.55% 9.99% 0.31% 99.54% / 98.53% 98.47% / 95.61% 6.325 cards 73.05%

Constructed decks (60 cards):

Lands P(S = 7) P(S = 6) P(S = 5) P(S = 4) P(4 lands on T4) P(5 lands on T5) mean hand P(8+ lands on T7)
20 39.38% 46.68% 11.46% 2.48% 76.88% / 67.69% 54.57% / 43.07% 6.230 cards 4.96%
21 42.47% 45.79% 9.91% 1.82% 80.26% / 71.60% 59.40% / 47.73% 6.289 cards 6.61%
22 45.40% 44.71% 8.55% 1.34% 83.22% / 75.03% 63.95% / 52.21% 6.342 cards 8.54%
23 48.11% 43.50% 7.39% 1.01% 85.74% / 78.17% 68.10% / 56.46% 6.387 cards 10.79%
24 50.54% 42.25% 6.44% 0.78% 87.93% / 80.92% 71.89% / 60.50% 6.425 cards 13.36%
25 52.61% 41.09% 5.70% 0.61% 89.79% / 83.29% 75.36% / 64.23% 6.457 cards 16.16%
26 54.40% 40.00% 5.10% 0.49% 91.38% / 85.40% 78.51% / 67.73% 6.483 cards 19.28%
27 55.78% 39.13% 4.69% 0.40% 92.72% / 87.28% 81.27% / 71.00% 6.503 cards 22.57%
28 56.79% 38.46% 4.41% 0.35% 93.85% / 88.95% 83.77% / 74.08% 6.517 cards 26.12%
29 57.43% 38.01% 4.25% 0.31% 94.85% / 90.36% 86.03% / 76.86% 6.526 cards 29.89%
30 57.61% 37.89% 4.22% 0.28% 95.67% / 91.63% 88.01% / 79.46% 6.528 cards 33.78%
31 57.40% 38.02% 4.31% 0.26% 96.37% / 92.76% 89.75% / 81.86% 6.526 cards 37.85%
32 56.79% 38.46% 4.50% 0.25% 97.00% / 93.76% 91.35% / 84.05% 6.518 cards 42.03%
33 55.76% 39.14% 4.86% 0.25% 97.54% / 94.66% 92.74% / 86.10% 6.504 cards 46.30%
34 54.38% 40.02% 5.35% 0.25% 97.99% / 95.44% 93.98% / 87.94% 6.485 cards 50.71%
35 52.61% 41.09% 6.04% 0.26% 98.37% / 96.18% 95.07% / 89.68% 6.460 cards 55.13%
36 50.52% 42.28% 6.92% 0.28% 98.71% / 96.82% 96.02% / 91.26% 6.430 cards 59.53%

Yorion decks (80 cards):

Lands P(S = 7) P(S = 6) P(S = 5) P(S = 4) P(4 lands on T4) P(5 lands on T5) mean hand P(8+ lands on T7)
25 35.14% 46.77% 13.79% 4.31% 71.00% / 61.58% 47.70% / 37.07% 6.127 cards 3.71%
26 37.57% 46.47% 12.52% 3.44% 74.13% / 64.90% 51.57% / 40.59% 6.182 cards 4.69%
27 39.92% 46.02% 11.33% 2.72% 76.96% / 68.02% 55.30% / 44.07% 6.231 cards 5.82%
28 42.14% 45.46% 10.22% 2.17% 79.47% / 70.89% 58.86% / 47.45% 6.276 cards 7.12%
29 44.31% 44.76% 9.20% 1.73% 81.79% / 73.55% 62.27% / 50.75% 6.317 cards 8.56%
30 46.35% 43.97% 8.29% 1.39% 83.88% / 76.00% 65.49% / 53.98% 6.353 cards 10.17%
31 48.22% 43.17% 7.49% 1.12% 85.68% / 78.23% 68.46% / 57.04% 6.385 cards 11.92%
32 49.98% 42.33% 6.78% 0.91% 87.36% / 80.30% 71.32% / 60.02% 6.414 cards 13.85%
33 51.50% 41.56% 6.19% 0.75% 88.83% / 82.18% 73.95% / 62.88% 6.438 cards 15.90%
34 52.91% 40.78% 5.68% 0.63% 90.13% / 83.89% 76.34% / 65.58% 6.460 cards 18.14%
35 54.08% 40.10% 5.28% 0.53% 91.30% / 85.46% 78.62% / 68.20% 6.477 cards 20.49%
36 55.07% 39.53% 4.95% 0.46% 92.35% / 86.85% 80.74% / 70.58% 6.492 cards 22.97%
37 55.86% 39.03% 4.71% 0.40% 93.26% / 88.14% 82.66% / 72.89% 6.504 cards 25.57%
38 56.43% 38.67% 4.55% 0.36% 94.09% / 89.29% 84.47% / 75.05% 6.512 cards 28.30%
39 56.75% 38.46% 4.46% 0.33% 94.78% / 90.36% 86.07% / 77.11% 6.516 cards 31.15%
40 56.86% 38.38% 4.45% 0.30% 95.45% / 91.35% 87.55% / 79.07% 6.518 cards 34.06%
41 56.74% 38.47% 4.51% 0.28% 96.01% / 92.24% 88.96% / 80.91% 6.517 cards 37.06%
42 56.42% 38.67% 4.63% 0.27% 96.53% / 93.05% 90.22% / 82.61% 6.512 cards 40.16%
43 55.84% 39.04% 4.85% 0.27% 96.98% / 93.79% 91.36% / 84.25% 6.505 cards 43.26%
44 55.07% 39.51% 5.16% 0.27% 97.40% / 94.47% 92.46% / 85.76% 6.494 cards 46.50%
45 54.10% 40.09% 5.54% 0.26% 97.75% / 95.08% 93.39% / 87.19% 6.480 cards 49.70%
46 52.94% 40.75% 6.03% 0.27% 98.06% / 95.67% 94.26% / 88.55% 6.464 cards 52.96%
47 51.52% 41.53% 6.67% 0.28% 98.36% / 96.19% 95.07% / 89.83% 6.443 cards 56.20%
48 49.94% 42.36% 7.40% 0.30% 98.62% / 96.68% 95.76% / 90.96% 6.419 cards 59.45%

Commander decks (99 cards):

Lands P(S = 7) P(S = 6) P(S = 5) P(S = 4) P(4 lands on T4) P(5 lands on T5) mean hand P(8+ lands on T7)
33 63.56% 27.90% 6.82% 1.72% 80.76% 58.79% 6.533 cards 6.54%
34 65.69% 26.79% 6.12% 1.41% 82.57% 61.51% 6.568 cards 7.60%
35 67.68% 25.71% 5.48% 1.14% 84.22% 64.12% 6.599 cards 8.74%
36 69.58% 24.59% 4.91% 0.93% 85.74% 66.61% 6.628 cards 9.99%
37 71.31% 23.55% 4.39% 0.75% 87.11% 68.95% 6.654 cards 11.35%
38 72.86% 22.55% 3.96% 0.63% 88.33% 71.10% 6.676 cards 12.78%
39 74.23% 21.68% 3.57% 0.52% 89.43% 73.18% 6.696 cards 14.33%
40 75.51% 20.83% 3.23% 0.44% 90.43% 75.14% 6.714 cards 15.90%
41 76.62% 20.05% 2.96% 0.36% 91.37% 77.02% 6.729 cards 17.60%
42 77.63% 19.34% 2.73% 0.30% 92.18% 78.71% 6.743 cards 19.36%
43 78.44% 18.75% 2.53% 0.27% 92.91% 80.34% 6.754 cards 21.24%
44 79.16% 18.23% 2.38% 0.23% 93.59% 81.85% 6.763 cards 23.17%
45 79.76% 17.78% 2.25% 0.21% 94.22% 83.30% 6.771 cards 25.23%
46 80.23% 17.43% 2.15% 0.19% 94.79% 84.67% 6.777 cards 27.32%
47 80.58% 17.16% 2.09% 0.17% 95.27% 85.87% 6.781 cards 29.42%
48 80.80% 17.00% 2.04% 0.15% 95.75% 87.07% 6.785 cards 31.69%
49 80.95% 16.89% 2.02% 0.14% 96.17% 88.19% 6.786 cards 33.97%
50 80.92% 16.92% 2.02% 0.14% 96.55% 89.21% 6.786 cards 36.26%
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment