1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
//! Auctions and bidding during the first phase of the game.

use std::fmt;
use std::str::FromStr;

use super::game;
use super::cards;
use super::pos;

use rustc_serialize;

/// Goal set by a contract.
///
/// Determines the winning conditions and the score on success.
#[derive(PartialEq,Clone,Copy,Debug)]
pub enum Target {
    /// Team must get 80 points
    Contract80,
    /// Team must get 90 points
    Contract90,
    /// Team must get 100 points
    Contract100,
    /// Team must get 110 points
    Contract110,
    /// Team must get 120 points
    Contract120,
    /// Team must get 130 points
    Contract130,
    /// Team must get 140 points
    Contract140,
    /// Team must get 150 points
    Contract150,
    /// Team must get 160 points
    Contract160,
    /// Team must win all tricks
    ContractCapot,
}

impl rustc_serialize::Encodable for Target {
    fn encode<S: rustc_serialize::Encoder>(&self, s: &mut S) -> Result<(), S::Error> {
        s.emit_str(self.to_str())
    }
}

impl rustc_serialize::Decodable for Target {
    fn decode<D: rustc_serialize::Decoder>(d: &mut D) -> Result<Self, D::Error> {
        let content = try!(d.read_str());
        Self::from_str(&content).map_err(|s| d.error(&s))
    }
}


impl Target {
    /// Returns the score this target would give on success.
    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",
        }
    }

    /// Determines whether this target was reached.
    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_string()
    }
}

/// Contract taken by a team.
///
/// Composed of a trump suit and a target to reach.
#[derive(Clone,Debug,RustcEncodable,RustcDecodable)]
pub struct Contract {
    /// Initial author of the contract.
    pub author: pos::PlayerPos,
    /// Trump suit for this game.
    pub trump: cards::Suit,
    /// Target for the contract.
    pub target: Target,
    /// Level of coinche:
    ///
    /// * `0`: not coinched
    /// * `1`: coinched
    /// * `2`: surcoinched
    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,
        }
    }
}

/// Current state of an auction
#[derive(PartialEq,Clone,Copy,Debug)]
pub enum AuctionState {
    /// Players are still bidding for the highest contract
    Bidding,
    /// One player coinched, maybe another one will surcoinche?
    Coinching,
    /// Auction is over, game will begin
    Over,
    /// No contract was taken, a new game will start
    Cancelled,
}

/// Represents the entire auction process.
pub struct Auction {
    history: Vec<Contract>,
    pass_count: usize,
    first: pos::PlayerPos,
    state: AuctionState,
    players: [cards::Hand; 4],
}

/// Possible error occuring during an Auction.
#[derive(PartialEq,Debug)]
pub enum BidError {
    /// The auction was closed and does not accept more contracts.
    AuctionClosed,
    /// A player tried bidding before his turn.
    TurnError,
    /// The given bid was not higher than the previous one.
    NonRaisedTarget,
    /// Cannot complete the auction when it is still running.
    AuctionRunning,
    /// No contract was offered during the auction, it cannot complete.
    NoContract,
    /// The contract was coinched too many times.
    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 {
    /// Starts a new auction, starting with the player `first`.
    pub fn new(first: pos::PlayerPos) -> Self {
        Auction {
            history: Vec::new(),
            pass_count: 0,
            state: AuctionState::Bidding,
            first: first,
            players: super::deal_hands(),
        }
    }

    /// Returns the current state of the auctions.
    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(())
    }

    /// Returns the player that is expected to play next.
    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)
    }

    /// Bid a new, higher contract.
    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 we're all the way to the top, there's nowhere else to go
        if target == Target::ContractCapot {
            self.state = AuctionState::Coinching;
        }

        let contract = Contract::new(pos, trump, target);
        self.history.push(contract);
        self.pass_count = 0;

        // Only stops the bids if the guy asked for a capot
        Ok(self.state)
    }

    /// Look at the last offered contract.
    ///
    /// Returns `None` if no contract was offered yet.
    pub fn current_contract(&self) -> Option<&Contract> {
        if self.history.is_empty() {
            None
        } else {
            Some(&self.history[self.history.len() - 1])
        }
    }

    /// Returns the players cards.
    pub fn hands(&self) -> [cards::Hand; 4] {
        self.players
    }

    /// The current player passes his turn.
    ///
    /// Returns the new auction state :
    ///
    /// * `AuctionState::Cancelled` if all players passed
    /// * `AuctionState::Over` if 3 players passed in a row
    /// * The previous state otherwise
    pub fn pass(&mut self, pos: pos::PlayerPos) -> Result<AuctionState, BidError> {
        if pos != self.next_player() {
            return Err(BidError::TurnError);
        }

        self.pass_count += 1;

        // After 3 passes, we're back to the contract author, and we can start.
        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)
    }

    /// Attempt to coinche the current contract.
    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;
        // Stop if we are already sur-coinching
        self.state = if self.history[i].coinche_level == 2 {
            AuctionState::Over
        } else {
            AuctionState::Coinching
        };

        Ok(self.state)
    }

    /// Consumes a complete auction to enter the second game phase.
    ///
    /// If the auction was ready, returns `Ok<GameState>`
    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);

        // First three people pass.
        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));

        // Someone bids.
        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));
        // Partner surbids
        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),
            _ => {}
        }
    }
}