I implemented a simple CLI based rock-paper-scissors game in Rust. The player plays against the computer. Each game has three rounds, not counting draws.
Cargo.toml
[package]
name = "rps"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
rand = "0.8.5"
lib.rs
use rand::{
distributions::{Distribution, Standard},
Rng,
};
use std::cmp::Ordering;
use std::fmt::{Debug, Display, Formatter};
use std::str::FromStr;
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum RPS {
Rock,
Paper,
Scissors,
}
impl Display for RPS {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}",
match self {
Self::Rock => '🪨',
Self::Paper => '🧻',
Self::Scissors => '✀',
}
)
}
}
impl PartialOrd<Self> for RPS {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
match (self, other) {
(Self::Rock, Self::Paper) => Some(Ordering::Less),
(Self::Rock, Self::Scissors) => Some(Ordering::Greater),
(Self::Paper, Self::Rock) => Some(Ordering::Greater),
(Self::Paper, Self::Scissors) => Some(Ordering::Less),
(Self::Scissors, Self::Rock) => Some(Ordering::Less),
(Self::Scissors, Self::Paper) => Some(Ordering::Greater),
(_, _) => None,
}
}
}
impl Ord for RPS {
fn cmp(&self, other: &Self) -> Ordering {
match self.partial_cmp(other) {
Some(order) => order,
None => Ordering::Equal,
}
}
}
impl Distribution<RPS> for Standard {
fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> RPS {
match rng.gen_range(0..=2) {
0 => RPS::Rock,
1 => RPS::Paper,
_ => RPS::Scissors,
}
}
}
impl FromStr for RPS {
type Err = &'static str;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.trim().to_lowercase().as_str() {
"r" => Ok(Self::Rock),
"p" => Ok(Self::Paper),
"s" => Ok(Self::Scissors),
_ => Err("invalid string"),
}
}
}
main.rs
use rand::random;
use rps::RPS;
use std::cmp::Ordering;
use std::io::{stdin, stdout, Write};
use std::str::FromStr;
fn main() {
let mut rounds: u8 = 0;
let mut score: i8 = 0;
loop {
if rounds >= 3 {
break;
}
let computer: RPS = random();
let user = read_user();
match user.cmp(&computer) {
Ordering::Equal => {
println!("Draw: {} = {}", user, computer);
continue;
}
Ordering::Less => {
println!("You lost: {} < {}", user, computer);
score -= 1;
}
Ordering::Greater => {
println!("You won: {} > {}", user, computer);
score += 1;
}
}
rounds += 1;
}
match score.cmp(&0) {
Ordering::Equal => println!("The game ended in a draw."),
Ordering::Less => println!("The computer won the game."),
Ordering::Greater => println!("The player won the game."),
}
}
fn read_user() -> RPS {
loop {
print!("Your selection (r/p/s): ");
stdout().flush().expect("Could not flush STDOUT.");
let mut buf = String::new();
if stdin().read_line(&mut buf).is_ok() {
match RPS::from_str(&buf) {
Ok(rps) => return rps,
Err(error) => println!("{}: {}", error, buf),
}
}
}
}
I'd like to have feedback on the general code style and especially the use of trait implementations in lib.rs
.