Go Fish/Rust

From Rosetta Code
Go Fish/Rust is part of Go_Fish. You may find other members of Go_Fish at Category:Go_Fish.

Rust implementation of Go Fish.

use rand::thread_rng;
use rand::seq::SliceRandom;
use std::io::Write;

// parameters
const STARTING_HAND_SIZE: usize = 9;
const NUM_SUITS: usize = 4;

struct Deck {
    cards: Vec<String>,
}

impl Deck {
    fn new() -> Self {
        Self { // initializes deck with cards
            cards: {
                let mut deck = vec![];
                for i in "2 3 4 5 6 7 8 9 10 J Q K A".split(" ") {
                    for _ in 0..NUM_SUITS {
                        deck.push(String::from(i));
                    }
                }
                deck
            }
        }
    }

    fn shuffle(&mut self) {
        self.cards.shuffle(&mut thread_rng());
    }

    fn done(&self) -> bool {
        self.cards.len() == 0
    }

    fn draw(& mut self) -> String {
        self.cards.pop().unwrap()
    }
}

struct Player {
    hand: Vec<String>,
    score: usize,
    name: String,
}

impl Player {
    fn new(deck: &mut Deck, name: &str) -> Self {
        // initializes player, draws starting hand
        let mut p = Self {
            hand: vec![],
            score: 0,
            name: String::from(name),
        };
        for _ in 0..STARTING_HAND_SIZE {
            p.add(deck.draw(), deck);
        }
        p
    }

    fn done(&self) -> bool {
        self.hand.len() == 0
    }

    fn draw(&mut self, deck: &mut Deck) {
        self.add(deck.draw(), deck);
    }

    fn has(&self, card: &String) -> bool {
        self.hand.contains(card)
    }

    fn take(&mut self, card: &String) -> Option<String> {
        if self.has(card) {
            let mut i = 0;
            for c in &self.hand {
                if c == card {
                    break;
                }
                i += 1;
            }
            Some(self.hand.remove(i))
        } else {
            None
        }
    }

    fn add(&mut self, card: String, deck: &mut Deck) {
        // adds card to hand. If you have NUM_SUITS, get a book. If you have no cards, draw.
        if self.hand.contains(&card) {
            let mut i = 0;
            for item in &self.hand {
                if item == &card {
                    i += 1;
                }
            }
            if i == NUM_SUITS-1 {
                while self.has(&card) { self.take(&card); }
                self.score += 1;
                if self.done() && !deck.done() {
                    self.draw(deck);
                }
                return;
            }
        }
        self.hand.push(card);
        self.hand.sort();
    }
}

fn input(string: &str) -> String {
    print!("{}", string);
    std::io::stdout().flush().unwrap();
    let mut line = String::new();
    std::io::stdin().read_line(&mut line).unwrap();
    line.pop(); // \n\r
    line.pop();
    line
}

fn main() {
    let mut thread = thread_rng();
    let mut deck = Deck::new();
    deck.shuffle();
    let mut user = Player::new(&mut deck, &input("Enter your name: "));
    let mut npc = Player::new(&mut deck, "NPC");

    let mut has_guessed = vec![];

    let mut player_turn = true; // toggle

    while !deck.done() {
        println!("\n\ndeck size: {}", deck.cards.len());
        if player_turn {
            while !deck.done() {
                print!("\n== {}'s Turn ==\n{}: {}\nNPC: {}\nYOUR HAND: ", user.name, user.name, user.score, npc.score);
                for card in &user.hand {
                    print!("{} ", card);
                }

                let mut choice = input("\nYou fish for a: ");
                while !"2 3 4 5 6 7 8 9 10 J Q K A".split(" ").collect::<Vec<&str>>().contains(&choice.as_str()) || !user.hand.contains(&choice) {
                    println!("invalid response, expecting 2, 3, 4, 5, ..., Q, K, A. The card must be in your hand.");
                    choice = input("You fish for a: ");
                }
                if !has_guessed.contains(&choice) { has_guessed.push(choice.clone()); }
                match npc.take(&choice) {
                    Some(thing) => {
                        user.add(thing, &mut deck);
                        while npc.has(&choice) {
                            user.add(npc.take(&choice).unwrap(), &mut deck);
                        }
                        println!("Yes, go again");
                    }
                    None => {
                        user.draw(&mut deck);
                        println!("Go fish!");
                        break;
                    }
                }
            }
        } else {
            while !deck.done() {
                print!("\n== NPC's Turn ==\n{}: {}\nNPC: {}\nYOUR HAND: ", user.name, user.score, npc.score);
                for card in &user.hand {
                    print!("{} ", card);
                }

                // guess if user has guessed & you have
                let mut choice = String::new();
                for string in &has_guessed {
                    if npc.hand.contains(string) {
                        choice = string.clone();
                    }
                }
                if &choice == "" {
                    choice = npc.hand.choose(&mut thread).unwrap().clone();
                }

                println!("\nDo you have a {}?", choice);
                match user.take(&choice) {
                    Some(thing) => {
                        println!("Yes, I do.");
                        npc.add(thing, &mut deck);
                        while user.has(&choice) {
                            npc.add(user.take(&choice).unwrap(), &mut deck);
                        }
                        input("\n...");
                    }
                    None => {
                        npc.draw(&mut deck);
                        println!("Go fish!");
                        input("\n...");
                        break;
                    }
                }
            }
        }
        player_turn = !player_turn;
    }
    if deck.done() {
        println!("Deck Empty, Game Over");
    }
    if user.score > npc.score {
        println!("{} has won with a score of {}", user.name, user.score);
    } else if user.score == npc.score {
        println!("{} and {} have tied with a score of {}", user.name, npc.name, user.score);
    } else {
        println!("{} has won with a score of {}", npc.name, npc.score);
    }
}