My game engine for playing a new game: FEMTO

โš“ Rust    ๐Ÿ“… 2025-08-07    ๐Ÿ‘ค surdeus    ๐Ÿ‘๏ธ 5      

surdeus


use std::collections::HashMap;
use rand::seq::{IndexedRandom, SliceRandom};

const fn wins_round(played: u8, other: u8) -> bool {
    (played > other && 2 * other > played) || 2 * played < other
}

/// The main engine function. Evaluates a list of cards that will produce the card with the most probable win outcome.
fn engine(my_cards: &[u8], opp_cards: &[u8], can_recurse: bool) -> u8 {
    let mut rng = rand::rng();
    let mut solution_list = HashMap::new();
    for card in my_cards {
        solution_list.insert(
            card,
            opp_cards
                .iter()
                .filter(|x| wins_round(*card, **x))
                .count()
        );
    }
    let main_solutions = solution_list
        .iter()
        .filter(|(_, value)| *value == solution_list.values().max().expect("solution_list should not be empty"))
        .map(|(key, _)| *key)
        .collect::<Vec<_>>();
    if main_solutions.len() > 1 && can_recurse {
        let mut solutions_that_win_round = Vec::new();
        let engine_value = engine(opp_cards, my_cards, false);
        for &main_solution in &main_solutions {
            if wins_round(*main_solution, engine_value) {
                solutions_that_win_round.push(*main_solution);
            }
        }
        let return_value = solutions_that_win_round.choose(&mut rng).unwrap_or(main_solutions.choose(&mut rng).expect("Ideally you shouldn't have an empty list like that."));
        *return_value
    } else {
        **main_solutions
            .choose(&mut rng)
            .expect("main_solutions should not be empty")
    }
}

fn main() {
    let mut rng = rand::rng();
    let mut buffer = String::new();
    let mut all_cards = [2, 3, 4, 5, 6, 7, 8, 10];
    all_cards.shuffle(&mut rng);
    let mut player_1_cards = all_cards[..4].to_vec();
    let mut player_2_cards = all_cards[4..].to_vec();
    let mut my_played_card: u8;
    let mut your_played_card: u8;
    let mut my_trophies = Vec::new();
    let mut your_trophies = Vec::new();
    while !player_1_cards.is_empty() && !player_2_cards.is_empty() {
        println!("My cards: {:?}", player_1_cards);
        println!("Your cards: {:?}", player_2_cards);
        println!("My trophies: {:?}", my_trophies);
        println!("Your trophies: {:?}", your_trophies);
        my_played_card = engine(&player_1_cards, &player_2_cards, true);
        your_played_card = loop {
            println!("What card do you wish to play?");
            std::io::stdin().read_line(&mut buffer).unwrap();
            let ans = buffer.trim().parse::<u8>();
            match ans {
                Ok(answer) => {
                    if !player_2_cards.contains(&answer) {
                        println!("You do not have {} in your cards. Do you wish to play another card?", answer);
                        continue;
                    }
                }
                Err(_) => {
                    println!("Uh-oh, your answer isn't a number!");
                    continue;
                }
            };
            break ans.unwrap();
        };
        buffer.clear();
        println!("I played: {}", my_played_card);
        println!("You played: {}", your_played_card);
        player_1_cards
            .remove(
                player_1_cards
                    .iter()
                    .position(|card| *card == my_played_card)
                    .expect(&format!("Uh-oh, my engine has a problem! It shouldn't give me {}", my_played_card))
            );
        player_2_cards
            .remove(
                player_2_cards
                    .iter()
                    .position(|card| *card == your_played_card)
                    .unwrap()
            );
        if wins_round(my_played_card, your_played_card) {
            println!("My card won!");
            let card_to_be_trophied = engine(&[my_played_card, your_played_card], &player_1_cards, false);
            if card_to_be_trophied == my_played_card {
                my_trophies.push(my_played_card);
                player_2_cards.push(your_played_card);
                println!("I choose to trophy {} and give you {}", my_played_card, your_played_card);
            } else if card_to_be_trophied == your_played_card {
                my_trophies.push(your_played_card);
                player_2_cards.push(my_played_card);
                println!("I choose to trophy {} and give you {}", your_played_card, my_played_card);
            } else {
                panic!("My engine is not working!");
            }
        } else {
            println!("Your card won!");
            let card_to_be_trophied = loop {
                println!("What card do you wish to add to your trophies?");
                std::io::stdin().read_line(&mut buffer).unwrap();
                let ans = buffer.trim().parse::<u8>();
                match ans {
                    Ok(answer) => {
                        if my_played_card != answer && your_played_card != answer {
                            println!("Uh-oh, neither of us played {}.", answer);
                            continue;
                        }
                    }
                    Err(_) => {
                        println!("Uh-oh, your answer isn't a number!");
                        continue;
                    }
                }
                break ans.unwrap();
            };
            buffer.clear();
            if card_to_be_trophied == your_played_card {
                your_trophies.push(your_played_card);
                player_1_cards.push(my_played_card);
                println!("You gave me {} and trophied {}", my_played_card, your_played_card);
            } else if card_to_be_trophied == my_played_card {
                your_trophies.push(my_played_card);
                player_1_cards.push(your_played_card);
                println!("You gave me {} and trophied {}", your_played_card, my_played_card);
            }
        }
    }
    println!("My trophies: {:?}", my_trophies);
    println!("Your trophies: {:?}", your_trophies);
    if my_trophies.len() > your_trophies.len() {
        println!("I won!");
    } else if your_trophies.len() > my_trophies.len() {
        println!("You won!");
    } else {
        println!("It's a draw.");
    }
}

Rules of Femto:

  1. Femto is a card game for two players; a Femto pack consists of eight cards numbered 2,3,4,5,6,7,8,10.

  2. The cards are shuffled and dealt, so each player gets four cards.

  3. In each round of play each player puts out one card, face down. The two cards are then turned face up.

  4. The round is won by the higher value card, unless the higher card is more than twice the value of the lower, in which case the lower card wins. e.g. 10 beats 8, 6 beats 5, 3 beats 10, 10 beats 5, โ€ฆ

  5. Whoever plays the winning card chooses one of the two cards and puts it, face up, on the table in front of him/her. The player of the losing card takes the remaining card and puts it back into his/her hand.

  6. More rounds are played until one player has no cards left.

  7. The winner is the player with the greater total value of cards in front of them at the end of the hand.

Whatโ€™s your opinion?

1 post - 1 participant

Read full topic

๐Ÿท๏ธ Rust_feed