use std::fmt;
use std::str::FromStr;
use super::game;
use super::cards;
use super::pos;
#[derive(PartialEq,Clone,Copy,Debug,Serialize,Deserialize)]
pub enum Target {
Contract80,
Contract90,
Contract100,
Contract110,
Contract120,
Contract130,
Contract140,
Contract150,
Contract160,
ContractCapot,
}
impl Target {
pub fn score(&self) -> i32 {
match *self {
Target::Contract80 => 80,
Target::Contract90 => 90,
Target::Contract100 => 100,
Target::Contract110 => 110,
Target::Contract120 => 120,
Target::Contract130 => 130,
Target::Contract140 => 140,
Target::Contract150 => 150,
Target::Contract160 => 160,
Target::ContractCapot => 250,
}
}
pub fn to_str(&self) -> &'static str {
match *self {
Target::Contract80 => "80",
Target::Contract90 => "90",
Target::Contract100 => "100",
Target::Contract110 => "110",
Target::Contract120 => "120",
Target::Contract130 => "130",
Target::Contract140 => "140",
Target::Contract150 => "150",
Target::Contract160 => "160",
Target::ContractCapot => "Capot",
}
}
pub fn victory(&self, points: i32, capot: bool) -> bool {
match *self {
Target::ContractCapot => capot,
other => points >= other.score(),
}
}
}
impl FromStr for Target {
type Err = String;
fn from_str(s: &str) -> Result<Self, String> {
match s {
"80" => Ok(Target::Contract80),
"90" => Ok(Target::Contract90),
"100" => Ok(Target::Contract100),
"110" => Ok(Target::Contract110),
"120" => Ok(Target::Contract120),
"130" => Ok(Target::Contract130),
"140" => Ok(Target::Contract140),
"150" => Ok(Target::Contract150),
"160" => Ok(Target::Contract160),
"Capot" => Ok(Target::ContractCapot),
_ => Err(format!("invalid target: {}", s)),
}
}
}
impl ToString for Target {
fn to_string(&self) -> String {
self.to_str().to_owned()
}
}
#[derive(Clone,Debug,Serialize,Deserialize)]
pub struct Contract {
pub author: pos::PlayerPos,
pub trump: cards::Suit,
pub target: Target,
pub coinche_level: i32,
}
impl Contract {
fn new(author: pos::PlayerPos, trump: cards::Suit, target: Target) -> Self {
Contract {
author: author,
trump: trump,
target: target,
coinche_level: 0,
}
}
}
#[derive(PartialEq,Clone,Copy,Debug)]
pub enum AuctionState {
Bidding,
Coinching,
Over,
Cancelled,
}
pub struct Auction {
history: Vec<Contract>,
pass_count: usize,
first: pos::PlayerPos,
state: AuctionState,
players: [cards::Hand; 4],
}
#[derive(PartialEq,Debug)]
pub enum BidError {
AuctionClosed,
TurnError,
NonRaisedTarget,
AuctionRunning,
NoContract,
OverCoinche,
}
impl fmt::Display for BidError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
BidError::AuctionClosed => write!(f, "auctions are closed"),
BidError::TurnError => write!(f, "invalid turn order"),
BidError::NonRaisedTarget => write!(f, "bid must be higher than current contract"),
BidError::AuctionRunning => write!(f, "the auction are still running"),
BidError::NoContract => write!(f, "no contract was offered"),
BidError::OverCoinche => write!(f, "contract is already sur-coinched"),
}
}
}
impl Auction {
pub fn new(first: pos::PlayerPos) -> Self {
Auction {
history: Vec::new(),
pass_count: 0,
state: AuctionState::Bidding,
first: first,
players: super::deal_hands(),
}
}
pub fn get_state(&self) -> AuctionState {
self.state
}
fn can_bid(&self, target: Target) -> Result<(), BidError> {
if self.state != AuctionState::Bidding {
return Err(BidError::AuctionClosed);
}
if !self.history.is_empty() &&
target.score() <= self.history[self.history.len() - 1].target.score() {
return Err(BidError::NonRaisedTarget);
}
Ok(())
}
pub fn next_player(&self) -> pos::PlayerPos {
let base = if let Some(contract) = self.history.last() {
contract.author.next()
} else {
self.first
};
base.next_n(self.pass_count)
}
pub fn bid(&mut self,
pos: pos::PlayerPos,
trump: cards::Suit,
target: Target)
-> Result<AuctionState, BidError> {
if pos != self.next_player() {
return Err(BidError::TurnError);
}
try!(self.can_bid(target));
if target == Target::ContractCapot {
self.state = AuctionState::Coinching;
}
let contract = Contract::new(pos, trump, target);
self.history.push(contract);
self.pass_count = 0;
Ok(self.state)
}
pub fn current_contract(&self) -> Option<&Contract> {
if self.history.is_empty() {
None
} else {
Some(&self.history[self.history.len() - 1])
}
}
pub fn hands(&self) -> [cards::Hand; 4] {
self.players
}
pub fn pass(&mut self, pos: pos::PlayerPos) -> Result<AuctionState, BidError> {
if pos != self.next_player() {
return Err(BidError::TurnError);
}
self.pass_count += 1;
if !self.history.is_empty() {
if self.pass_count >= 3 {
self.state = AuctionState::Over;
}
} else if self.pass_count >= 4 {
self.state = AuctionState::Cancelled;
};
Ok(self.state)
}
pub fn coinche(&mut self, pos: pos::PlayerPos) -> Result<AuctionState, BidError> {
if pos != self.next_player() {
return Err(BidError::TurnError);
}
if self.history.is_empty() {
return Err(BidError::NoContract);
}
let i = self.history.len() - 1;
if self.history[i].coinche_level > 1 {
return Err(BidError::OverCoinche);
}
self.history[i].coinche_level += 1;
self.state = if self.history[i].coinche_level == 2 {
AuctionState::Over
} else {
AuctionState::Coinching
};
Ok(self.state)
}
pub fn complete(&mut self) -> Result<game::GameState, BidError> {
if self.state != AuctionState::Over {
Err(BidError::AuctionRunning)
} else if self.history.is_empty() {
Err(BidError::NoContract)
} else {
Ok(game::GameState::new(self.first,
self.players,
self.history.pop().expect("contract history empty")))
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use {cards, pos};
#[test]
fn test_auction() {
let mut auction = Auction::new(pos::PlayerPos::P0);
assert!(auction.state == AuctionState::Bidding);
assert_eq!(auction.pass(pos::PlayerPos::P0), Ok(AuctionState::Bidding));
assert_eq!(auction.pass(pos::PlayerPos::P1), Ok(AuctionState::Bidding));
assert_eq!(auction.pass(pos::PlayerPos::P2), Ok(AuctionState::Bidding));
assert_eq!(auction.pass(pos::PlayerPos::P1), Err(BidError::TurnError));
assert_eq!(auction.coinche(pos::PlayerPos::P2),
Err(BidError::TurnError));
assert_eq!(auction.bid(pos::PlayerPos::P3, cards::Suit::Heart, Target::Contract80),
Ok(AuctionState::Bidding));
assert_eq!(auction.bid(pos::PlayerPos::P0, cards::Suit::Club, Target::Contract80)
.err(),
Some(BidError::NonRaisedTarget));
assert_eq!(auction.bid(pos::PlayerPos::P1, cards::Suit::Club, Target::Contract100)
.err(),
Some(BidError::TurnError));
assert_eq!(auction.pass(pos::PlayerPos::P0), Ok(AuctionState::Bidding));
assert_eq!(auction.bid(pos::PlayerPos::P1, cards::Suit::Heart, Target::Contract100),
Ok(AuctionState::Bidding));
assert_eq!(auction.pass(pos::PlayerPos::P2), Ok(AuctionState::Bidding));
assert_eq!(auction.pass(pos::PlayerPos::P3), Ok(AuctionState::Bidding));
assert_eq!(auction.pass(pos::PlayerPos::P0), Ok(AuctionState::Over));
assert!(auction.state == AuctionState::Over);
match auction.complete() {
Err(_) => assert!(false),
_ => {}
}
}
}