use core::num;
use std::{
cmp,
collections::{BTreeMap, BTreeSet, btree_map::Entry},
usize,
};
use crate::{
Money, Value,
animals::{Animal, AnimalSet},
messages::{
actions::{
AuctionDecision, Bidding, InitialTrade, NoAction, PlayerTurnDecision, SendMoney,
TradeOpponentDecision,
},
game_updates::{
AuctionKind, AuctionRound, GameUpdate, MoneyTrade, MoneyTransfer, TradeOffer,
},
},
player::{base_player::PlayerId, player_actions::PlayerActions, wallet::Wallet},
};
use float_ord::FloatOrd;
use rand::{Rng, SeedableRng};
use rand_chacha::ChaCha8Rng;
#[derive(Default, Debug)]
pub struct ValueOwned {
pub max: Value,
pub min: Value,
}
impl ValueOwned {
pub fn new(wallet: &Wallet) -> Self {
let exact = wallet.total_money();
ValueOwned {
max: exact,
min: exact,
}
}
pub fn add(&mut self, value: Value) {
self.max += value;
self.min += value;
}
pub fn sub(&mut self, value: Value) {
self.max = self.max.saturating_sub(value);
self.min = self.min.saturating_sub(value);
}
pub fn value_at_step(&self, percentage: f32) -> Value {
let value = (self.min as f32 + self.max as f32 * percentage) as usize;
value
}
}
#[derive(Default)]
pub struct SimplePlayer {
id: PlayerId,
opponents: BTreeMap<PlayerId, (BTreeMap<Animal, usize>, ValueOwned)>,
wallet: Wallet,
owned_animals: BTreeMap<Animal, usize>,
all_animals: Vec<(Animal, usize)>,
mean_points: usize,
pub risk: f32,
previous_subjective_values: Vec<f32>,
}
impl PlayerActions for SimplePlayer {
fn _draw_or_trade(&mut self) -> PlayerTurnDecision {
if let Some((is_good, possible_trade)) = self.trade_helper() {
if is_good {
return PlayerTurnDecision::Trade(possible_trade);
}
}
PlayerTurnDecision::Draw()
}
fn _trade(&mut self) -> InitialTrade {
self.trade_helper().unwrap().1
}
fn _provide_bidding(&mut self, state: AuctionRound) -> Bidding {
let value_allowed_to_spend = (self.wallet.total_money() as f32 * self.risk) as usize;
let empty_id = "".to_string();
let (highest_bidder_id, &highest_bid) =
Self::get_highest_bid(&state.bids).unwrap_or((&empty_id, &0));
if highest_bidder_id == &self.id || highest_bid > value_allowed_to_spend {
return Bidding::Pass();
}
let current_subj_value = Self::subjective_animal_value_for_player(
&self.all_animals,
&self.owned_animals,
&state.animal,
1,
);
let averaged_subj_values = self.average_subj_value_over_last(5);
if Self::sqrt_points_for_player(&self.all_animals, &self.owned_animals)
<= self.mean_points as f32
|| (highest_bid <= value_allowed_to_spend && averaged_subj_values < current_subj_value)
{
let bidding_rounded_to_next_ten = highest_bid / 10 * 10; let my_bid = bidding_rounded_to_next_ten + 10;
return Bidding::Bid(my_bid);
}
Bidding::Pass()
}
fn _buy_or_sell(&mut self, state: AuctionRound) -> AuctionDecision {
let value_allowed_to_spend = (self.wallet.total_money() as f32 * self.risk) as usize;
let averaged_subj_values = self.average_subj_value_over_last(5);
let (_, &highest_bid) = Self::get_highest_bid(&state.bids).unwrap_or((&"".to_string(), &0));
let current_subj_value = Self::subjective_animal_value_for_player(
&self.all_animals,
&self.owned_animals,
&state.animal,
1,
);
if highest_bid <= value_allowed_to_spend
&& (Self::sqrt_points_for_player(&self.all_animals, &self.owned_animals)
<= self.mean_points as f32
|| averaged_subj_values < current_subj_value)
{
return AuctionDecision::Buy();
}
AuctionDecision::Sell()
}
fn _send_money_to_player(
&mut self,
player: &crate::player::base_player::PlayerId,
amount: crate::Value,
) -> SendMoney {
let bill_combination = self.get_bill_combination(amount);
SendMoney::Amount(bill_combination)
}
fn _respond_to_trade(&mut self, offer: TradeOffer) -> TradeOpponentDecision {
let my_subj_value = Self::subjective_animal_value_for_player(
&self.all_animals,
&self.owned_animals,
&offer.animal,
offer.animal_count,
);
let opponent_subj_value = Self::subjective_animal_value_for_player(
&self.all_animals,
&self.opponents.get(&offer.challenger).unwrap().0,
&offer.animal,
offer.animal_count,
);
if my_subj_value >= opponent_subj_value {
let value_allowed_to_spend = (self.wallet.total_money() as f32 * self.risk) as usize;
let bill_combination = self.get_bill_combination(value_allowed_to_spend);
TradeOpponentDecision::CounterOffer(bill_combination);
}
TradeOpponentDecision::Accept()
}
fn _receive_game_update(&mut self, update: GameUpdate) -> NoAction {
match update {
GameUpdate::Auction(auction_kind) => self.handle_update_auction(auction_kind),
GameUpdate::Trade {
challenger,
opponent,
animal,
animal_count,
receiver,
money_trade,
} => self.handle_update_trade(
challenger,
opponent,
animal,
animal_count,
receiver,
money_trade,
),
GameUpdate::Start {
wallet,
players_in_turn_order,
animals,
} => self.handle_update_start(wallet, players_in_turn_order, animals),
GameUpdate::End {
ranking,
illegal_moves_made,
} => (),
GameUpdate::ExposePlayer { player, wallet } => {
self.handle_update_expose(player, wallet)
}
GameUpdate::Inflation(money) => self.handle_update_inflation(money),
}
NoAction::Ok()
}
}
impl SimplePlayer {
pub fn handle_update_auction(&mut self, auction_kind: AuctionKind) {
let traded_animal;
match auction_kind {
AuctionKind::NoBiddings { host_id, animal } => {
traded_animal = animal.clone();
if self.id == host_id {
self.owned_animals
.entry(animal)
.and_modify(|count: &mut usize| *count += 1)
.or_insert(1);
} else {
self.opponents.entry(host_id).and_modify(|(animals, _)| {
animals
.entry(animal)
.and_modify(|count: &mut usize| *count += 1)
.or_insert(1);
});
}
}
AuctionKind::NormalAuction {
rounds,
from,
to,
money_transfer,
} => {
traded_animal = *rounds.animal.clone();
match money_transfer {
MoneyTransfer::Public {
card_amount: _,
min_value,
} => {
self.handle_exchange_payer(from, &rounds.animal, 1, min_value);
self.handle_exchange_seller(to, &rounds.animal, 0, min_value);
}
MoneyTransfer::Private { amount } => {
let total_value: usize = amount.iter().sum();
if self.id == from {
let could_pay = self.wallet.withdraw(&amount);
if could_pay.is_err() {
self.wallet = Wallet::default();
panic!("miscalculated my own wallet")
}
self.owned_animals
.entry(*rounds.animal)
.and_modify(|count: &mut usize| *count += 1)
.or_insert(1);
self.handle_exchange_seller(to, &rounds.animal, 0, total_value);
} else {
self.wallet.deposit(&amount);
self.handle_exchange_payer(from, &rounds.animal, 1, total_value);
}
}
}
}
}
self.previous_subjective_values
.push(Self::subjective_animal_value_for_player(
&self.all_animals,
&self.owned_animals,
&traded_animal,
1,
));
}
pub fn handle_update_trade(
&mut self,
challenger: PlayerId,
opponent: PlayerId,
animal: Animal,
animal_count: usize,
receiver: PlayerId,
money_trade: MoneyTrade,
) {
let looser = if receiver == challenger {
opponent.clone()
} else {
challenger.clone()
};
match money_trade {
MoneyTrade::Public {
challenger_card_offer,
opponent_card_offer,
} => {
self.handle_exchange_payer(receiver, &animal, animal_count, 0);
self.handle_exchange_seller(looser, &animal, animal_count, 0);
}
MoneyTrade::Private {
challenger_card_offer,
opponent_card_offer,
} => {
let opponent_payed_value = match &opponent_card_offer {
Some(amount) => amount.iter().sum::<usize>(),
None => 0,
};
let total_difference: usize = (challenger_card_offer.iter().sum::<usize>() as isize
- opponent_payed_value as isize)
.abs() as usize;
if self.id == receiver {
self.owned_animals
.entry(animal)
.and_modify(|count| *count += animal_count)
.or_insert(1);
self.handle_exchange_seller(looser, &animal, animal_count, total_difference);
} else {
if animal_count > 0 {
let current_count = self.owned_animals.get(&animal).unwrap();
let new_count = current_count.saturating_sub(animal_count);
if new_count > 0 {
self.owned_animals.insert(animal, new_count);
} else {
self.owned_animals.remove(&animal);
}
}
self.handle_exchange_payer(receiver, &animal, animal_count, total_difference);
}
if self.id == challenger {
let _ = self.wallet.withdraw(&challenger_card_offer);
opponent_card_offer.map(|amount| self.wallet.deposit(&amount));
} else {
opponent_card_offer.map(|amount| self.wallet.withdraw(&amount));
self.wallet.deposit(&challenger_card_offer);
}
}
}
}
pub fn handle_update_start(
&mut self,
wallet: Wallet,
players_in_turn_order: Vec<PlayerId>,
animals: Vec<AnimalSet>,
) {
self.opponents = players_in_turn_order
.iter()
.cloned()
.filter(|p| *p != self.id)
.map(|p| (p, (BTreeMap::default(), ValueOwned::new(&wallet))))
.collect();
self.wallet = wallet;
self.owned_animals = BTreeMap::default();
let mut all_animals = Vec::default();
for set in animals {
all_animals.push((set.animal().clone(), set.occurrences()));
}
self.all_animals = all_animals;
self.mean_points = self.estimate_average_points_per_player();
}
pub fn handle_update_expose(&mut self, player: PlayerId, wallet: Wallet) {
self.opponents
.entry(player)
.and_modify(|(_, old_wallet)| *old_wallet = ValueOwned::new(&wallet));
}
pub fn handle_update_inflation(&mut self, money: Money) {
self.wallet.add_money(money);
}
}
impl SimplePlayer {
pub fn new(id: String, risk: f32) -> Self {
if 0.0 > risk || 1.0 < risk {
panic!("pick risk between 0 and 1, currently {}", risk)
}
SimplePlayer {
id: id,
opponents: BTreeMap::default(),
wallet: Wallet::default(),
owned_animals: BTreeMap::default(),
all_animals: Vec::default(),
mean_points: 0,
risk: risk,
previous_subjective_values: Vec::default(),
}
}
pub fn new_from_seed(id: String, seed: u64) -> Self {
let risk = Self::get_random_risk(seed);
SimplePlayer::new(id, risk)
}
pub fn get_random_risk(seed: u64) -> f32 {
let mut rng = ChaCha8Rng::seed_from_u64(seed);
let risk: f32 = rng.random_range(0.0..=1.0);
risk
}
pub fn handle_exchange_payer(
&mut self,
paying_player: PlayerId,
animal: &Animal,
animal_count: usize,
min_value_payed: Value,
) {
self.opponents
.entry(paying_player)
.and_modify(|(animals, value_owned)| {
animals
.entry(*animal)
.and_modify(|count| *count += animal_count)
.or_insert(1);
value_owned.sub(min_value_payed)
});
}
pub fn handle_exchange_seller(
&mut self,
selling_player: PlayerId,
animal: &Animal,
animal_count: usize,
min_value_payed: Value,
) {
self.opponents
.entry(selling_player.clone())
.and_modify(|(animals, value_owned)| {
if animal_count > 0 {
let current_count = animals.get(animal).unwrap();
let new_count = current_count.saturating_sub(animal_count);
animals.insert(*animal, new_count);
}
value_owned.add(min_value_payed)
});
if let Entry::Occupied(mut player_entry) = self.opponents.entry(selling_player) {
let (animals, _) = player_entry.get_mut();
if let Entry::Occupied(animal_entry) = animals.entry(animal.clone()) {
let count = animal_entry.get();
if count == &0usize {
animal_entry.remove();
}
}
}
}
pub fn estimate_average_points_per_player(&mut self) -> usize {
let mut total_points: usize = 0;
let num_players = self.opponents.len() + 1;
for (animal, _) in self.all_animals.iter() {
total_points += animal.value()
}
let summed_card_points_per_player = total_points.div_ceil(num_players);
let final_points_per_player = summed_card_points_per_player as f32
* self.all_animals.len() as f32
/ num_players as f32;
final_points_per_player as usize
}
pub fn sqrt_points_for_player(
all_animals: &Vec<(Animal, usize)>,
animals: &BTreeMap<Animal, usize>,
) -> f32 {
let mut sqrt_points: f32 = 0.0;
for (animal, occurrences) in all_animals.iter() {
let my_animal_count = animals.get(animal);
if my_animal_count.is_some() {
let percentage_of_deck_owned =
*my_animal_count.unwrap() as f32 / *occurrences as f32;
sqrt_points += percentage_of_deck_owned.sqrt() * animal.value() as f32;
}
}
sqrt_points * animals.len() as f32
}
pub fn subjective_animal_value_for_player(
all_animals: &Vec<(Animal, usize)>,
animals: &BTreeMap<Animal, usize>,
animal: &Animal,
animal_count: usize,
) -> f32 {
let points_without = Self::sqrt_points_for_player(all_animals, animals);
let mut temp_animals = animals.clone();
temp_animals
.entry(*animal)
.and_modify(|current_count| *current_count += animal_count)
.or_insert(animal_count);
let points_with = Self::sqrt_points_for_player(all_animals, &temp_animals);
points_with - points_without
}
pub fn sorted_opponent_trades(&self) -> BTreeSet<(FloatOrd<f32>, PlayerId, Animal, usize)> {
let mut sorted_trades: BTreeSet<(FloatOrd<f32>, PlayerId, Animal, usize)> =
BTreeSet::default();
let all_animals = &self.all_animals;
let owned_animals = &self.owned_animals;
for (id, (animals, _)) in self.opponents.iter() {
for (animal, opponent_animal_count) in animals.iter() {
if let Some(my_animal_count) = self.owned_animals.get(animal) {
let trade_count = cmp::min(my_animal_count, opponent_animal_count);
let opponent_value = Self::subjective_animal_value_for_player(
all_animals,
&animals,
animal,
*trade_count,
);
let my_value = Self::subjective_animal_value_for_player(
all_animals,
&owned_animals,
animal,
*trade_count,
);
let total_value_change = my_value + opponent_value;
sorted_trades.insert((
FloatOrd(total_value_change),
id.clone(),
animal.clone(),
*trade_count,
));
}
}
}
sorted_trades
}
pub fn get_bill_combination(&self, amount: Value) -> Vec<Money> {
let possible_combinations = self.wallet.propose_bill_combinations(amount, false);
match possible_combinations.first() {
Some((_, bill_combination)) => bill_combination.clone(),
None => Vec::new(),
}
}
pub fn get_highest_bid(bids: &Vec<(PlayerId, Bidding)>) -> Option<(&PlayerId, &Value)> {
match bids.iter().max_by_key(|(_, bid)| bid) {
Some((player_id, Bidding::Bid(value))) => Some((player_id, value)),
Some(&(_, Bidding::Pass())) => None,
None => None,
}
}
pub fn trade_helper(&mut self) -> Option<(bool, InitialTrade)> {
let value_save_to_spend = (self.wallet.total_money() as f32 * self.risk) as usize;
let mut sub_optimal_trade: Option<InitialTrade> = None;
let sorted_trades = self.sorted_opponent_trades();
for (_, opponent_id, animal, animal_count) in sorted_trades.iter() {
let (_, opponent_value) = self.opponents.get(opponent_id).unwrap();
if opponent_value.max <= value_save_to_spend {
let bill_combination = self.get_bill_combination(opponent_value.max);
let save_trade = InitialTrade {
opponent: opponent_id.clone(),
animal: *animal,
animal_count: *animal_count,
amount: bill_combination.clone(),
};
return Some((true, save_trade));
}
if sub_optimal_trade.is_none() && opponent_value.max <= self.wallet.total_money() {
let bill_combination = self.get_bill_combination(opponent_value.value_at_step(0.5));
sub_optimal_trade = Some(InitialTrade {
opponent: opponent_id.clone(),
animal: *animal,
animal_count: *animal_count,
amount: bill_combination.clone(),
});
}
}
if sub_optimal_trade.is_some() {
return Some((false, sub_optimal_trade.unwrap()));
}
match sorted_trades.last() {
Some(_) => {
let (_, opponent_id, animal, animal_count) = sorted_trades.last().unwrap();
let (money, _) = self
.wallet
.bank_notes()
.last_key_value()
.unwrap_or((&0, &0));
Some((
false,
InitialTrade {
opponent: opponent_id.clone(),
animal: animal.clone(),
animal_count: *animal_count,
amount: vec![money.clone()],
},
))
}
None => None,
}
}
pub fn average_subj_value_over_last(&self, count: usize) -> f32 {
let mut averaged_subj_values = 0.0;
if self.previous_subjective_values.len() > count {
let average_from_index = self.previous_subjective_values.len() - count;
averaged_subj_values = self.previous_subjective_values[average_from_index..]
.iter()
.sum::<f32>()
/ count as f32;
} else if self.previous_subjective_values.len() > 0 {
averaged_subj_values = self.previous_subjective_values.iter().sum::<f32>()
/ self.previous_subjective_values.len() as f32;
}
averaged_subj_values
}
}