Initial commit
This commit is contained in:
commit
afaf8d669d
|
|
@ -0,0 +1,2 @@
|
|||
/target
|
||||
.vscode
|
||||
|
|
@ -0,0 +1,192 @@
|
|||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "ansi_term"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b"
|
||||
dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "atty"
|
||||
version = "0.2.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
|
||||
dependencies = [
|
||||
"hermit-abi",
|
||||
"libc",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "1.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
|
||||
|
||||
[[package]]
|
||||
name = "bulls-and-cows"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"clap",
|
||||
"rand",
|
||||
"text_io",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "2.33.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "37e58ac78573c40708d45522f0d80fa2f01cc4f9b4e2bf749807255454312002"
|
||||
dependencies = [
|
||||
"ansi_term",
|
||||
"atty",
|
||||
"bitflags",
|
||||
"strsim",
|
||||
"textwrap",
|
||||
"unicode-width",
|
||||
"vec_map",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7fcd999463524c52659517fe2cea98493cfe485d10565e7b0fb07dbba7ad2753"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"wasi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hermit-abi"
|
||||
version = "0.1.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.97"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "12b8adadd720df158f4d70dfe7ccc6adb0472d7c55ca83445f6a5ab3e36f8fb6"
|
||||
|
||||
[[package]]
|
||||
name = "ppv-lite86"
|
||||
version = "0.2.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857"
|
||||
|
||||
[[package]]
|
||||
name = "rand"
|
||||
version = "0.8.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2e7573632e6454cf6b99d7aac4ccca54be06da05aca2ef7423d22d27d4d4bcd8"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"rand_chacha",
|
||||
"rand_core",
|
||||
"rand_hc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_chacha"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
|
||||
dependencies = [
|
||||
"ppv-lite86",
|
||||
"rand_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_core"
|
||||
version = "0.6.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_hc"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d51e9f596de227fda2ea6c84607f5558e196eeaf43c986b724ba4fb8fdf497e7"
|
||||
dependencies = [
|
||||
"rand_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "strsim"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
|
||||
|
||||
[[package]]
|
||||
name = "text_io"
|
||||
version = "0.1.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6cb170b4f47dc48835fbc56259c12d8963e542b05a24be2e3a1f5a6c320fd2d4"
|
||||
|
||||
[[package]]
|
||||
name = "textwrap"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060"
|
||||
dependencies = [
|
||||
"unicode-width",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-width"
|
||||
version = "0.1.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3"
|
||||
|
||||
[[package]]
|
||||
name = "vec_map"
|
||||
version = "0.8.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191"
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.10.2+wasi-snapshot-preview1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6"
|
||||
|
||||
[[package]]
|
||||
name = "winapi"
|
||||
version = "0.3.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
|
||||
dependencies = [
|
||||
"winapi-i686-pc-windows-gnu",
|
||||
"winapi-x86_64-pc-windows-gnu",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi-i686-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
|
||||
|
||||
[[package]]
|
||||
name = "winapi-x86_64-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
[package]
|
||||
name = "bulls-and-cows"
|
||||
version = "0.1.0"
|
||||
edition = "2018"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
rand = "0.8.4"
|
||||
text_io = "0.1.8"
|
||||
clap = "2.33.3"
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
Copyright (c) 2022 Stanislav Nikolov <hello@snikolov.me>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
|
@ -0,0 +1,309 @@
|
|||
use std::{
|
||||
collections::HashSet,
|
||||
convert::TryInto,
|
||||
fs::File,
|
||||
io::{self, BufRead, Write},
|
||||
};
|
||||
|
||||
use clap::{App, Arg, ArgMatches};
|
||||
use rand::Rng;
|
||||
use text_io::read;
|
||||
|
||||
type Guess = String;
|
||||
|
||||
struct GuessComparator {}
|
||||
|
||||
impl GuessComparator {
|
||||
fn compare<'a>(target: &Guess, guess: Guess) -> GuessResult {
|
||||
let mut bulls = 0;
|
||||
let mut target_cows = HashSet::new();
|
||||
let mut guess_cows = HashSet::new();
|
||||
|
||||
for ab in target.chars().into_iter().zip(guess.chars()) {
|
||||
if ab.0 == ab.1 {
|
||||
bulls += 1;
|
||||
} else {
|
||||
target_cows.insert(ab.0);
|
||||
guess_cows.insert(ab.1);
|
||||
}
|
||||
}
|
||||
let cows = target_cows.intersection(&guess_cows).count().try_into().unwrap();
|
||||
|
||||
GuessResult::new(guess, bulls, cows)
|
||||
}
|
||||
|
||||
fn is_consistent_with_guesses(
|
||||
guess: &Guess,
|
||||
guess_vec: &[GuessResult],
|
||||
) -> bool {
|
||||
!guess_vec
|
||||
.iter()
|
||||
.any(|g| &Self::compare(guess, g.guess.clone()) != g)
|
||||
}
|
||||
}
|
||||
|
||||
trait GuessGenerator {
|
||||
fn with_len(len: u32) -> Self
|
||||
where
|
||||
Self: Sized;
|
||||
fn get_len(&self) -> u32;
|
||||
fn generate(&self) -> Guess;
|
||||
|
||||
fn parse(&self, s: &str) -> Result<Guess, &'static str>;
|
||||
|
||||
fn is_win(&self, guess: &GuessResult) -> bool {
|
||||
self.get_len() == guess.bulls
|
||||
}
|
||||
}
|
||||
|
||||
struct WordGuessGenerator {
|
||||
len: u32,
|
||||
words: Vec<String>,
|
||||
}
|
||||
|
||||
impl GuessGenerator for WordGuessGenerator {
|
||||
fn with_len(len: u32) -> Self
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
const DICT_FILENAME: &str = "/usr/share/dict/words";
|
||||
|
||||
let file = File::open(DICT_FILENAME).unwrap();
|
||||
let words: Vec<_> = io::BufReader::new(file)
|
||||
.lines()
|
||||
.filter(|line| {
|
||||
if let Ok(line) = line {
|
||||
let mut digits = HashSet::new();
|
||||
line.chars().for_each(|c| {
|
||||
digits.insert(c);
|
||||
});
|
||||
|
||||
line.len() == len.try_into().unwrap()
|
||||
&& line.len() == digits.len()
|
||||
&& digits.into_iter().all(|d| ('a'..='z').contains(&d))
|
||||
} else {
|
||||
false
|
||||
}
|
||||
})
|
||||
.map(|line| line.unwrap().to_lowercase())
|
||||
.collect();
|
||||
|
||||
Self { len, words }
|
||||
}
|
||||
|
||||
fn get_len(&self) -> u32 {
|
||||
self.len
|
||||
}
|
||||
|
||||
fn generate(&self) -> Guess {
|
||||
let index = rand::thread_rng().gen_range(0..self.words.len());
|
||||
self.words[index].clone()
|
||||
}
|
||||
|
||||
fn parse(&self, s: &str) -> Result<Guess, &'static str> {
|
||||
if self.words.contains(&s.to_string()) {
|
||||
Ok(Guess::from(s))
|
||||
} else {
|
||||
Err("Invalid value")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct NumberGuessGenerator {
|
||||
len: u32,
|
||||
}
|
||||
|
||||
impl GuessGenerator for NumberGuessGenerator {
|
||||
fn with_len(len: u32) -> Self {
|
||||
Self { len }
|
||||
}
|
||||
|
||||
fn get_len(&self) -> u32 {
|
||||
self.len
|
||||
}
|
||||
|
||||
fn parse(&self, s: &str) -> Result<Guess, &'static str> {
|
||||
let mut digits = HashSet::new();
|
||||
s.chars().for_each(|c| {
|
||||
digits.insert(c);
|
||||
});
|
||||
|
||||
if s.len() == self.len.try_into().unwrap()
|
||||
&& s.len() == digits.len()
|
||||
&& digits.into_iter().all(|d| ('1'..='9').contains(&d))
|
||||
{
|
||||
Ok(Guess::from(s))
|
||||
} else {
|
||||
Err("Not a valid value")
|
||||
}
|
||||
}
|
||||
|
||||
fn generate(&self) -> Guess {
|
||||
let mut rng = rand::thread_rng();
|
||||
loop {
|
||||
let low: i32 = 10i32.pow(self.len - 1);
|
||||
let high: i32 = 10i32.pow(self.len);
|
||||
let n = rng.gen_range(low..high);
|
||||
|
||||
let guess = self.parse(&n.to_string());
|
||||
if let Ok(guess) = guess {
|
||||
return guess;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||
struct GuessResult {
|
||||
guess: Guess,
|
||||
bulls: u32,
|
||||
cows: u32,
|
||||
}
|
||||
|
||||
impl GuessResult {
|
||||
fn new(guess: Guess, bulls: u32, cows: u32) -> GuessResult {
|
||||
GuessResult {
|
||||
guess,
|
||||
bulls,
|
||||
cows,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn run_ai_guess_program(gen: Box<dyn GuessGenerator>, number: &str) {
|
||||
let number = gen.parse(number).expect("Invalid number");
|
||||
let mut guess_vec = Vec::new();
|
||||
loop {
|
||||
let guess = gen.generate();
|
||||
if !GuessComparator::is_consistent_with_guesses(&guess, &guess_vec) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let result = GuessComparator::compare(&number, guess);
|
||||
|
||||
if gen.is_win(&result) {
|
||||
println!("{}.", result.guess);
|
||||
break;
|
||||
} else {
|
||||
println!("{} -> {} bulls, {} cows", result.guess, result.bulls, result.cows);
|
||||
};
|
||||
|
||||
guess_vec.push(result);
|
||||
}
|
||||
}
|
||||
|
||||
fn run_user_guess_program(gen: Box<dyn GuessGenerator>) {
|
||||
let number = gen.generate();
|
||||
let mut guess_vec = Vec::new();
|
||||
|
||||
loop {
|
||||
let guess: String;
|
||||
loop {
|
||||
print!("Enter a guess: ");
|
||||
std::io::stdout().flush().unwrap();
|
||||
|
||||
let g: String = read!("{}");
|
||||
if g == "?" {
|
||||
println!("The answer was: {}", number);
|
||||
return;
|
||||
} else if g == "q" {
|
||||
return;
|
||||
} else if let Ok(g) = gen.parse(&g) {
|
||||
guess = g;
|
||||
break;
|
||||
} else {
|
||||
println!();
|
||||
}
|
||||
}
|
||||
|
||||
let result = GuessComparator::compare(&number, guess);
|
||||
let is_win = gen.is_win(&result);
|
||||
guess_vec.push(result);
|
||||
guess_vec.iter().enumerate().for_each(|(i, g)| {
|
||||
println!(
|
||||
"{}. {} -> {} bulls, {} cows",
|
||||
i + 1,
|
||||
g.guess,
|
||||
g.bulls,
|
||||
g.cows
|
||||
)
|
||||
});
|
||||
|
||||
if is_win {
|
||||
println!("You win!");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn read_args() -> ArgMatches<'static> {
|
||||
App::new("Bulls and Cows")
|
||||
.version("1.0")
|
||||
.author("Stanislav Nikolov <hello@snikolov.me>")
|
||||
.about("A bulls and cows implementation.")
|
||||
.arg(
|
||||
Arg::with_name("variant")
|
||||
.required(true)
|
||||
.possible_values(&["number", "words"])
|
||||
.short("variant")
|
||||
.long("variant")
|
||||
.value_name("VARIANT")
|
||||
.help("Sets the game variant")
|
||||
.takes_value(true),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("length")
|
||||
.long("length")
|
||||
.value_name("LENGTH")
|
||||
.help("Sets the guess length")
|
||||
.default_value("4")
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("run-ai")
|
||||
.long("run-ai")
|
||||
.value_name("GUESS")
|
||||
.help("Runs the AI against the given guess")
|
||||
.takes_value(true)
|
||||
)
|
||||
.get_matches()
|
||||
}
|
||||
fn main() {
|
||||
let args = read_args();
|
||||
|
||||
let run_ai = args.value_of("run-ai");
|
||||
let len: u32 = if let Some(guess) = run_ai {
|
||||
guess.len().try_into().unwrap()
|
||||
} else {
|
||||
args.value_of("length").unwrap().parse().unwrap()
|
||||
};
|
||||
let gen: Box<dyn GuessGenerator> = match args.value_of("variant").unwrap() {
|
||||
"number" => Box::new(NumberGuessGenerator::with_len(len)),
|
||||
"words" => Box::new(WordGuessGenerator::with_len(len)),
|
||||
_ => panic!("Unknown variant: {}", args.value_of("variant").unwrap()),
|
||||
};
|
||||
|
||||
if let Some(guess) = run_ai {
|
||||
run_ai_guess_program(gen, guess);
|
||||
} else {
|
||||
run_user_guess_program(gen);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::{Guess, GuessResult, GuessComparator};
|
||||
|
||||
#[test]
|
||||
fn test_matches() {
|
||||
let g1234 = Guess::from("1234");
|
||||
let g1432 = Guess::from("1432");
|
||||
let g4321 = Guess::from("4321");
|
||||
let g5678 = Guess::from("5678");
|
||||
|
||||
assert_eq!(GuessComparator::compare(&g1234, g5678.clone()), GuessResult::new(g5678.clone(), 0, 0));
|
||||
assert_eq!(GuessComparator::compare(&g1234, g5678.clone()), GuessResult::new(g5678.clone(), 0, 0));
|
||||
assert_eq!(GuessComparator::compare(&g1234, g4321.clone()), GuessResult::new(g4321.clone(), 0, 4));
|
||||
assert_eq!(GuessComparator::compare(&g1234, g1432.clone()), GuessResult::new(g1432.clone(), 2, 2));
|
||||
assert_eq!(GuessComparator::compare(&g1234, g1234.clone()), GuessResult::new(g1234.clone(), 4, 0));
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue