Make runs reproducible by replacing HashMaps with FnvHashMaps
This commit is contained in:
parent
e04d242a71
commit
ef860fa73b
7 changed files with 43 additions and 36 deletions
7
Cargo.lock
generated
7
Cargo.lock
generated
|
@ -3,6 +3,11 @@ name = "crossbeam"
|
||||||
version = "0.2.8"
|
version = "0.2.8"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "fnv"
|
||||||
|
version = "1.0.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "getopts"
|
name = "getopts"
|
||||||
version = "0.2.14"
|
version = "0.2.14"
|
||||||
|
@ -34,6 +39,7 @@ name = "rust_hanabi"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"crossbeam 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
"crossbeam 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"getopts 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)",
|
"getopts 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"log 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
"log 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)",
|
"rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
@ -41,6 +47,7 @@ dependencies = [
|
||||||
|
|
||||||
[metadata]
|
[metadata]
|
||||||
"checksum crossbeam 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "348228ce9f93d20ffc30c18e575f82fa41b9c8bf064806c65d41eba4771595a0"
|
"checksum crossbeam 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "348228ce9f93d20ffc30c18e575f82fa41b9c8bf064806c65d41eba4771595a0"
|
||||||
|
"checksum fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "2fad85553e09a6f881f739c29f0b00b0f01357c743266d478b68951ce23285f3"
|
||||||
"checksum getopts 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)" = "d9047cfbd08a437050b363d35ef160452c5fe8ea5187ae0a624708c91581d685"
|
"checksum getopts 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)" = "d9047cfbd08a437050b363d35ef160452c5fe8ea5187ae0a624708c91581d685"
|
||||||
"checksum libc 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)" = "4870ef6725dde13394134e587e4ab4eca13cb92e916209a31c851b49131d3c75"
|
"checksum libc 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)" = "4870ef6725dde13394134e587e4ab4eca13cb92e916209a31c851b49131d3c75"
|
||||||
"checksum log 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "038b5d13189a14e5b6ac384fdb7c691a45ef0885f6d2dddbf422e6c3506b8234"
|
"checksum log 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "038b5d13189a14e5b6ac384fdb7c691a45ef0885f6d2dddbf422e6c3506b8234"
|
||||||
|
|
|
@ -7,4 +7,5 @@ authors = ["Jeff Wu <wuthefwasthat@gmail.com>"]
|
||||||
rand = "*"
|
rand = "*"
|
||||||
log = "*"
|
log = "*"
|
||||||
getopts = "*"
|
getopts = "*"
|
||||||
|
fnv = "*"
|
||||||
crossbeam = "0.2.5"
|
crossbeam = "0.2.5"
|
||||||
|
|
22
src/game.rs
22
src/game.rs
|
@ -1,4 +1,4 @@
|
||||||
use std::collections::HashMap;
|
use fnv::FnvHashMap;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::ops::Range;
|
use std::ops::Range;
|
||||||
|
|
||||||
|
@ -46,11 +46,11 @@ impl fmt::Debug for Card {
|
||||||
|
|
||||||
#[derive(Debug,Clone)]
|
#[derive(Debug,Clone)]
|
||||||
pub struct CardCounts {
|
pub struct CardCounts {
|
||||||
counts: HashMap<Card, u32>,
|
counts: FnvHashMap<Card, u32>,
|
||||||
}
|
}
|
||||||
impl CardCounts {
|
impl CardCounts {
|
||||||
pub fn new() -> CardCounts {
|
pub fn new() -> CardCounts {
|
||||||
let mut counts = HashMap::new();
|
let mut counts = FnvHashMap::default();
|
||||||
for &color in COLORS.iter() {
|
for &color in COLORS.iter() {
|
||||||
for &value in VALUES.iter() {
|
for &value in VALUES.iter() {
|
||||||
counts.insert(Card::new(color, value), 0);
|
counts.insert(Card::new(color, value), 0);
|
||||||
|
@ -248,7 +248,7 @@ pub struct BoardState {
|
||||||
pub deck_size: u32,
|
pub deck_size: u32,
|
||||||
pub total_cards: u32,
|
pub total_cards: u32,
|
||||||
pub discard: Discard,
|
pub discard: Discard,
|
||||||
pub fireworks: HashMap<Color, Firework>,
|
pub fireworks: FnvHashMap<Color, Firework>,
|
||||||
|
|
||||||
pub num_players: u32,
|
pub num_players: u32,
|
||||||
|
|
||||||
|
@ -271,7 +271,7 @@ impl BoardState {
|
||||||
pub fn new(opts: &GameOptions, deck_size: u32) -> BoardState {
|
pub fn new(opts: &GameOptions, deck_size: u32) -> BoardState {
|
||||||
let fireworks = COLORS.iter().map(|&color| {
|
let fireworks = COLORS.iter().map(|&color| {
|
||||||
(color, Firework::new(color))
|
(color, Firework::new(color))
|
||||||
}).collect::<HashMap<_, _>>();
|
}).collect::<FnvHashMap<_, _>>();
|
||||||
|
|
||||||
BoardState {
|
BoardState {
|
||||||
deck_size: deck_size,
|
deck_size: deck_size,
|
||||||
|
@ -479,7 +479,7 @@ pub struct BorrowedGameView<'a> {
|
||||||
pub player: Player,
|
pub player: Player,
|
||||||
pub hand_size: usize,
|
pub hand_size: usize,
|
||||||
// the cards of the other players, as well as the information they have
|
// the cards of the other players, as well as the information they have
|
||||||
pub other_hands: HashMap<Player, &'a Cards>,
|
pub other_hands: FnvHashMap<Player, &'a Cards>,
|
||||||
// board state
|
// board state
|
||||||
pub board: &'a BoardState,
|
pub board: &'a BoardState,
|
||||||
}
|
}
|
||||||
|
@ -506,7 +506,7 @@ pub struct OwnedGameView {
|
||||||
pub player: Player,
|
pub player: Player,
|
||||||
pub hand_size: usize,
|
pub hand_size: usize,
|
||||||
// the cards of the other players, as well as the information they have
|
// the cards of the other players, as well as the information they have
|
||||||
pub other_hands: HashMap<Player, Cards>,
|
pub other_hands: FnvHashMap<Player, Cards>,
|
||||||
// board state
|
// board state
|
||||||
pub board: BoardState,
|
pub board: BoardState,
|
||||||
}
|
}
|
||||||
|
@ -515,7 +515,7 @@ impl OwnedGameView {
|
||||||
let other_hands = borrowed_view.other_hands.iter()
|
let other_hands = borrowed_view.other_hands.iter()
|
||||||
.map(|(&other_player, &player_state)| {
|
.map(|(&other_player, &player_state)| {
|
||||||
(other_player, player_state.clone())
|
(other_player, player_state.clone())
|
||||||
}).collect::<HashMap<_, _>>();
|
}).collect::<FnvHashMap<_, _>>();
|
||||||
|
|
||||||
OwnedGameView {
|
OwnedGameView {
|
||||||
player: borrowed_view.player.clone(),
|
player: borrowed_view.player.clone(),
|
||||||
|
@ -544,7 +544,7 @@ impl GameView for OwnedGameView {
|
||||||
// complete game state (known to nobody!)
|
// complete game state (known to nobody!)
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct GameState {
|
pub struct GameState {
|
||||||
pub hands: HashMap<Player, Cards>,
|
pub hands: FnvHashMap<Player, Cards>,
|
||||||
pub board: BoardState,
|
pub board: BoardState,
|
||||||
pub deck: Cards,
|
pub deck: Cards,
|
||||||
}
|
}
|
||||||
|
@ -582,7 +582,7 @@ impl GameState {
|
||||||
deck.pop().unwrap()
|
deck.pop().unwrap()
|
||||||
}).collect::<Vec<_>>();
|
}).collect::<Vec<_>>();
|
||||||
(player, hand)
|
(player, hand)
|
||||||
}).collect::<HashMap<_, _>>();
|
}).collect::<FnvHashMap<_, _>>();
|
||||||
|
|
||||||
GameState {
|
GameState {
|
||||||
hands: hands,
|
hands: hands,
|
||||||
|
@ -605,7 +605,7 @@ impl GameState {
|
||||||
|
|
||||||
// get the game state view of a particular player
|
// get the game state view of a particular player
|
||||||
pub fn get_view(&self, player: Player) -> BorrowedGameView {
|
pub fn get_view(&self, player: Player) -> BorrowedGameView {
|
||||||
let mut other_hands = HashMap::new();
|
let mut other_hands = FnvHashMap::default();
|
||||||
for (&other_player, hand) in &self.hands {
|
for (&other_player, hand) in &self.hands {
|
||||||
if player != other_player {
|
if player != other_player {
|
||||||
other_hands.insert(other_player, hand);
|
other_hands.insert(other_player, hand);
|
||||||
|
|
|
@ -3,6 +3,7 @@ extern crate getopts;
|
||||||
extern crate log;
|
extern crate log;
|
||||||
extern crate rand;
|
extern crate rand;
|
||||||
extern crate crossbeam;
|
extern crate crossbeam;
|
||||||
|
extern crate fnv;
|
||||||
|
|
||||||
mod helpers;
|
mod helpers;
|
||||||
mod game;
|
mod game;
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
use rand::{self, Rng, SeedableRng};
|
use rand::{self, Rng, SeedableRng};
|
||||||
use std::collections::HashMap;
|
use fnv::FnvHashMap;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use crossbeam;
|
use crossbeam;
|
||||||
|
|
||||||
|
@ -25,17 +25,15 @@ fn new_deck(seed: u32) -> Cards {
|
||||||
pub fn simulate_once(
|
pub fn simulate_once(
|
||||||
opts: &GameOptions,
|
opts: &GameOptions,
|
||||||
game_strategy: Box<GameStrategy>,
|
game_strategy: Box<GameStrategy>,
|
||||||
seed_opt: Option<u32>,
|
seed: u32,
|
||||||
) -> GameState {
|
) -> GameState {
|
||||||
|
|
||||||
let seed = seed_opt.unwrap_or(rand::thread_rng().next_u32());
|
|
||||||
let deck = new_deck(seed);
|
let deck = new_deck(seed);
|
||||||
|
|
||||||
let mut game = GameState::new(opts, deck);
|
let mut game = GameState::new(opts, deck);
|
||||||
|
|
||||||
let mut strategies = game.get_players().map(|player| {
|
let mut strategies = game.get_players().map(|player| {
|
||||||
(player, game_strategy.initialize(player, &game.get_view(player)))
|
(player, game_strategy.initialize(player, &game.get_view(player)))
|
||||||
}).collect::<HashMap<Player, Box<PlayerStrategy>>>();
|
}).collect::<FnvHashMap<Player, Box<PlayerStrategy>>>();
|
||||||
|
|
||||||
while !game.is_over() {
|
while !game.is_over() {
|
||||||
let player = game.board.player;
|
let player = game.board.player;
|
||||||
|
@ -69,14 +67,14 @@ pub fn simulate_once(
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
struct Histogram {
|
struct Histogram {
|
||||||
pub hist: HashMap<Score, u32>,
|
pub hist: FnvHashMap<Score, u32>,
|
||||||
pub sum: Score,
|
pub sum: Score,
|
||||||
pub total_count: u32,
|
pub total_count: u32,
|
||||||
}
|
}
|
||||||
impl Histogram {
|
impl Histogram {
|
||||||
pub fn new() -> Histogram {
|
pub fn new() -> Histogram {
|
||||||
Histogram {
|
Histogram {
|
||||||
hist: HashMap::new(),
|
hist: FnvHashMap::default(),
|
||||||
sum: 0,
|
sum: 0,
|
||||||
total_count: 0,
|
total_count: 0,
|
||||||
}
|
}
|
||||||
|
@ -127,7 +125,7 @@ pub fn simulate<T: ?Sized>(
|
||||||
progress_info: Option<u32>,
|
progress_info: Option<u32>,
|
||||||
) where T: GameStrategyConfig + Sync {
|
) where T: GameStrategyConfig + Sync {
|
||||||
|
|
||||||
let first_seed = first_seed_opt.unwrap_or(rand::thread_rng().next_u32());
|
let first_seed = first_seed_opt.unwrap_or_else(|| rand::thread_rng().next_u32());
|
||||||
|
|
||||||
let strat_config_ref = &strat_config;
|
let strat_config_ref = &strat_config;
|
||||||
crossbeam::scope(|scope| {
|
crossbeam::scope(|scope| {
|
||||||
|
@ -154,7 +152,7 @@ pub fn simulate<T: ?Sized>(
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let game = simulate_once(&opts, strat_config_ref.initialize(&opts), Some(seed));
|
let game = simulate_once(&opts, strat_config_ref.initialize(&opts), seed);
|
||||||
let score = game.score();
|
let score = game.score();
|
||||||
lives_histogram.insert(game.board.lives_remaining);
|
lives_histogram.insert(game.board.lives_remaining);
|
||||||
score_histogram.insert(score);
|
score_histogram.insert(score);
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
use std::cell::{RefCell};
|
use std::cell::{RefCell};
|
||||||
use std::collections::{HashMap, HashSet};
|
use fnv::{FnvHashMap, FnvHashSet};
|
||||||
|
|
||||||
use strategy::*;
|
use strategy::*;
|
||||||
use game::*;
|
use game::*;
|
||||||
|
@ -31,13 +31,13 @@ impl GameStrategyConfig for CheatingStrategyConfig {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct CheatingStrategy {
|
pub struct CheatingStrategy {
|
||||||
player_hands_cheat: Rc<RefCell<HashMap<Player, Cards>>>,
|
player_hands_cheat: Rc<RefCell<FnvHashMap<Player, Cards>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CheatingStrategy {
|
impl CheatingStrategy {
|
||||||
pub fn new() -> CheatingStrategy {
|
pub fn new() -> CheatingStrategy {
|
||||||
CheatingStrategy {
|
CheatingStrategy {
|
||||||
player_hands_cheat: Rc::new(RefCell::new(HashMap::new())),
|
player_hands_cheat: Rc::new(RefCell::new(FnvHashMap::default())),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -56,7 +56,7 @@ impl GameStrategy for CheatingStrategy {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct CheatingPlayerStrategy {
|
pub struct CheatingPlayerStrategy {
|
||||||
player_hands_cheat: Rc<RefCell<HashMap<Player, Cards>>>,
|
player_hands_cheat: Rc<RefCell<FnvHashMap<Player, Cards>>>,
|
||||||
me: Player,
|
me: Player,
|
||||||
}
|
}
|
||||||
impl CheatingPlayerStrategy {
|
impl CheatingPlayerStrategy {
|
||||||
|
@ -120,7 +120,7 @@ impl CheatingPlayerStrategy {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn find_useless_card(&self, view: &BorrowedGameView, hand: &Cards) -> Option<usize> {
|
fn find_useless_card(&self, view: &BorrowedGameView, hand: &Cards) -> Option<usize> {
|
||||||
let mut set: HashSet<Card> = HashSet::new();
|
let mut set: FnvHashSet<Card> = FnvHashSet::default();
|
||||||
|
|
||||||
for (i, card) in hand.iter().enumerate() {
|
for (i, card) in hand.iter().enumerate() {
|
||||||
if view.board.is_dead(card) {
|
if view.board.is_dead(card) {
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use std::collections::{HashMap, HashSet};
|
use fnv::{FnvHashMap, FnvHashSet};
|
||||||
use std::cmp::Ordering;
|
use std::cmp::Ordering;
|
||||||
|
|
||||||
use strategy::*;
|
use strategy::*;
|
||||||
|
@ -185,14 +185,14 @@ impl Question for AdditiveComboQuestion {
|
||||||
struct CardPossibilityPartition {
|
struct CardPossibilityPartition {
|
||||||
index: usize,
|
index: usize,
|
||||||
n_partitions: u32,
|
n_partitions: u32,
|
||||||
partition: HashMap<Card, u32>,
|
partition: FnvHashMap<Card, u32>,
|
||||||
}
|
}
|
||||||
impl CardPossibilityPartition {
|
impl CardPossibilityPartition {
|
||||||
fn new(
|
fn new(
|
||||||
index: usize, max_n_partitions: u32, card_table: &CardPossibilityTable, view: &OwnedGameView
|
index: usize, max_n_partitions: u32, card_table: &CardPossibilityTable, view: &OwnedGameView
|
||||||
) -> CardPossibilityPartition {
|
) -> CardPossibilityPartition {
|
||||||
let mut cur_block = 0;
|
let mut cur_block = 0;
|
||||||
let mut partition = HashMap::new();
|
let mut partition = FnvHashMap::default();
|
||||||
let mut n_partitions = 0;
|
let mut n_partitions = 0;
|
||||||
|
|
||||||
let has_dead = card_table.probability_is_dead(&view.board) != 0.0;
|
let has_dead = card_table.probability_is_dead(&view.board) != 0.0;
|
||||||
|
@ -288,7 +288,7 @@ impl GameStrategy for InformationStrategy {
|
||||||
view.board.get_players().map(|player| {
|
view.board.get_players().map(|player| {
|
||||||
let hand_info = HandInfo::new(view.board.hand_size);
|
let hand_info = HandInfo::new(view.board.hand_size);
|
||||||
(player, hand_info)
|
(player, hand_info)
|
||||||
}).collect::<HashMap<_,_>>();
|
}).collect::<FnvHashMap<_,_>>();
|
||||||
|
|
||||||
Box::new(InformationPlayerStrategy {
|
Box::new(InformationPlayerStrategy {
|
||||||
me: player,
|
me: player,
|
||||||
|
@ -301,7 +301,7 @@ impl GameStrategy for InformationStrategy {
|
||||||
|
|
||||||
pub struct InformationPlayerStrategy {
|
pub struct InformationPlayerStrategy {
|
||||||
me: Player,
|
me: Player,
|
||||||
public_info: HashMap<Player, HandInfo<CardPossibilityTable>>,
|
public_info: FnvHashMap<Player, HandInfo<CardPossibilityTable>>,
|
||||||
public_counts: CardCounts, // what any newly drawn card should be
|
public_counts: CardCounts, // what any newly drawn card should be
|
||||||
last_view: OwnedGameView, // the view on the previous turn
|
last_view: OwnedGameView, // the view on the previous turn
|
||||||
}
|
}
|
||||||
|
@ -550,8 +550,8 @@ impl InformationPlayerStrategy {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn find_useless_cards(&self, view: &OwnedGameView, hand: &HandInfo<CardPossibilityTable>) -> Vec<usize> {
|
fn find_useless_cards(&self, view: &OwnedGameView, hand: &HandInfo<CardPossibilityTable>) -> Vec<usize> {
|
||||||
let mut useless: HashSet<usize> = HashSet::new();
|
let mut useless: FnvHashSet<usize> = FnvHashSet::default();
|
||||||
let mut seen: HashMap<Card, usize> = HashMap::new();
|
let mut seen: FnvHashMap<Card, usize> = FnvHashMap::default();
|
||||||
|
|
||||||
for (i, card_table) in hand.iter().enumerate() {
|
for (i, card_table) in hand.iter().enumerate() {
|
||||||
if card_table.probability_is_dead(view.get_board()) == 1.0 {
|
if card_table.probability_is_dead(view.get_board()) == 1.0 {
|
||||||
|
@ -792,7 +792,7 @@ impl InformationPlayerStrategy {
|
||||||
(0 .. n - 1).into_iter().map(|i| { (player + 1 + i) % n }).collect()
|
(0 .. n - 1).into_iter().map(|i| { (player + 1 + i) % n }).collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_best_hint_of_options(&self, hint_player: Player, hint_option_set: HashSet<Hinted>) -> Hinted {
|
fn get_best_hint_of_options(&self, hint_player: Player, hint_option_set: FnvHashSet<Hinted>) -> Hinted {
|
||||||
let view = &self.last_view;
|
let view = &self.last_view;
|
||||||
|
|
||||||
// using hint goodness barely helps
|
// using hint goodness barely helps
|
||||||
|
@ -864,7 +864,7 @@ impl InformationPlayerStrategy {
|
||||||
2 => {
|
2 => {
|
||||||
// NOTE: this doesn't do that much better than just hinting
|
// NOTE: this doesn't do that much better than just hinting
|
||||||
// the first thing that doesn't match the hint_card
|
// the first thing that doesn't match the hint_card
|
||||||
let mut hint_option_set = HashSet::new();
|
let mut hint_option_set = FnvHashSet::default();
|
||||||
for card in hand {
|
for card in hand {
|
||||||
if card.color != hint_card.color {
|
if card.color != hint_card.color {
|
||||||
hint_option_set.insert(Hinted::Color(card.color));
|
hint_option_set.insert(Hinted::Color(card.color));
|
||||||
|
@ -889,7 +889,7 @@ impl InformationPlayerStrategy {
|
||||||
}
|
}
|
||||||
2 => {
|
2 => {
|
||||||
// Any value hint for a card other than the first
|
// Any value hint for a card other than the first
|
||||||
let mut hint_option_set = HashSet::new();
|
let mut hint_option_set = FnvHashSet::default();
|
||||||
for card in hand {
|
for card in hand {
|
||||||
if card.value != hint_card.value {
|
if card.value != hint_card.value {
|
||||||
hint_option_set.insert(Hinted::Value(card.value));
|
hint_option_set.insert(Hinted::Value(card.value));
|
||||||
|
@ -899,7 +899,7 @@ impl InformationPlayerStrategy {
|
||||||
}
|
}
|
||||||
3 => {
|
3 => {
|
||||||
// Any color hint for a card other than the first
|
// Any color hint for a card other than the first
|
||||||
let mut hint_option_set = HashSet::new();
|
let mut hint_option_set = FnvHashSet::default();
|
||||||
for card in hand {
|
for card in hand {
|
||||||
if card.color != hint_card.color {
|
if card.color != hint_card.color {
|
||||||
hint_option_set.insert(Hinted::Color(card.color));
|
hint_option_set.insert(Hinted::Color(card.color));
|
||||||
|
|
Loading…
Reference in a new issue