use std::fmt;
use std::str::FromStr;
use super::game;
use super::cards;
use super::pos;
use rustc_serialize;
#[derive(PartialEq,Clone,Copy)]
pub enum Target {
Contract80,
Contract90,
Contract100,
Contract110,
Contract120,
Contract130,
Contract140,
Contract150,
Contract160,
ContractCapot,
}
impl rustc_serialize::Encodable for Target {
fn encode<S: rustc_serialize::Encoder>(&self, s: &mut S) -> Result<(), S::Error> {
s.emit_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",
})
}
}
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 victory(&self, points: i32, capot: bool) -> bool {
match *self {
Target::Contract80 => points >= 80,
Target::Contract90 => points >= 90,
Target::Contract100 => points >= 100,
Target::Contract110 => points >= 110,
Target::Contract120 => points >= 120,
Target::Contract130 => points >= 130,
Target::Contract140 => points >= 140,
Target::Contract150 => points >= 150,
Target::Contract160 => points >= 160,
Target::ContractCapot => capot,
}
}
}
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)),
}
}
}
#[derive(Clone,RustcEncodable)]
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],
}
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(),
}
}
}
#[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 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() {
if 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);
}
match self.can_bid(target) {
Err(err) => return Err(err),
Ok(_) => (),
}
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::new_game(self.first, self.players, self.history.pop().expect("contract history empty")))
}
}
}
#[test]
fn test_auction() {
let mut auction = Auction::new(pos::PlayerPos(0));
assert!(auction.state == AuctionState::Bidding);
assert_eq!(auction.pass(pos::P0), Ok(AuctionState::Bidding));
assert_eq!(auction.pass(pos::P1), Ok(AuctionState::Bidding));
assert_eq!(auction.pass(pos::P2), Ok(AuctionState::Bidding));
assert_eq!(auction.pass(pos::P1), Err(BidError::TurnError));
assert_eq!(auction.coinche(pos::P2), Err(BidError::TurnError));
assert_eq!(auction.bid(
pos::P3,
cards::HEART,
Target::Contract80
), Ok(AuctionState::Bidding));
assert_eq!(auction.bid(
pos::P0,
cards::CLUB,
Target::Contract80,
).err(), Some(BidError::NonRaisedTarget));
assert_eq!(auction.bid(
pos::P1,
cards::CLUB,
Target::Contract100,
).err(), Some(BidError::TurnError));
assert_eq!(auction.pass(pos::P0), Ok(AuctionState::Bidding));
assert_eq!(auction.bid(
pos::P1,
cards::HEART,
Target::Contract100,
), Ok(AuctionState::Bidding));
assert_eq!(auction.pass(pos::P2), Ok(AuctionState::Bidding));
assert_eq!(auction.pass(pos::P3), Ok(AuctionState::Bidding));
assert_eq!(auction.pass(pos::P0), Ok(AuctionState::Over));
assert!(auction.state == AuctionState::Over);
match auction.complete() {
Err(_) => assert!(false),
_=> {},
}
}