├── src ├── play │ ├── mod.rs │ └── players │ │ ├── mod.rs │ │ ├── player.rs │ │ └── computer.rs ├── parser │ ├── mod.rs │ └── ab_parser.rs ├── letters.rs ├── lib.rs └── host.rs ├── .github ├── dependabot.yml └── workflows │ ├── ci-version.yml │ └── ci.yml ├── tests ├── ab_parser.rs └── host.rs ├── Cargo.toml ├── LICENSE ├── README.md ├── rustfmt.toml ├── examples ├── 4_digits.rs └── custom_2_players.rs └── .gitignore /src/play/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod players; 2 | -------------------------------------------------------------------------------- /src/parser/mod.rs: -------------------------------------------------------------------------------- 1 | mod ab_parser; 2 | 3 | pub use self::ab_parser::ABParser; 4 | -------------------------------------------------------------------------------- /src/play/players/mod.rs: -------------------------------------------------------------------------------- 1 | mod computer; 2 | mod player; 3 | 4 | pub use self::{computer::*, player::*}; 5 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: github-actions 4 | directory: "/" 5 | schedule: 6 | interval: "weekly" -------------------------------------------------------------------------------- /tests/ab_parser.rs: -------------------------------------------------------------------------------- 1 | use bulls_and_cows::parser::ABParser; 2 | 3 | #[test] 4 | fn parse_xayb() { 5 | let ab = ABParser::new(); 6 | 7 | assert_eq!((4, 0), ab.parse("4A0B").unwrap()); 8 | assert_eq!((2, 1), ab.parse("2a1B").unwrap()); 9 | assert_eq!((0, 4), ab.parse("0a4b").unwrap()); 10 | } 11 | -------------------------------------------------------------------------------- /src/letters.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashSet; 2 | 3 | /// To generate letters. 4 | pub struct Letters; 5 | 6 | impl Letters { 7 | /// Generate letters for numbers from 0 to 9. 8 | pub fn generate_numeric_letters() -> HashSet { 9 | let mut letters = HashSet::new(); 10 | 11 | for i in 0..=9 { 12 | letters.insert(i); 13 | } 14 | 15 | letters 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "bulls-and-cows" 3 | version = "1.0.23" 4 | authors = ["Magic Len "] 5 | edition = "2021" 6 | rust-version = "1.80" 7 | repository = "https://github.com/magiclen/bulls-and-cows" 8 | homepage = "https://magiclen.org/bulls-and-cows" 9 | keywords = ["bulls-and-cows", "game-1a2b", "number", "game-four-digits", "guess"] 10 | categories = ["game-engines"] 11 | description = "A framework for building bulls-and-cows games (1A2B) for any data type." 12 | license = "MIT" 13 | include = ["src/**/*", "Cargo.toml", "README.md", "LICENSE"] 14 | 15 | [dependencies] 16 | rand = "0.9" 17 | random-pick = "1.2.17" 18 | regex = "1" 19 | permutohedron = "0.2" 20 | debug-helper = "0.3" -------------------------------------------------------------------------------- /tests/host.rs: -------------------------------------------------------------------------------- 1 | #[test] 2 | fn random_answer() { 3 | let host = bulls_and_cows::Host::build_with_random_answer( 4 | bulls_and_cows::Letters::generate_numeric_letters(), 5 | 4, 6 | ) 7 | .unwrap(); 8 | 9 | assert_eq!(4, host.get_answer_length()); 10 | } 11 | 12 | #[test] 13 | fn known_answer() { 14 | let host = bulls_and_cows::Host::build_with_known_answer( 15 | bulls_and_cows::Letters::generate_numeric_letters(), 16 | vec![1, 2, 3, 4], 17 | ) 18 | .unwrap(); 19 | 20 | assert_eq!((4, 0), host.answer(&[1, 2, 3, 4]).unwrap()); 21 | assert_eq!((2, 2), host.answer(&[1, 2, 4, 3]).unwrap()); 22 | assert_eq!((0, 4), host.answer(&[4, 3, 2, 1]).unwrap()); 23 | assert_eq!((0, 0), host.answer(&[5, 6, 7, 8]).unwrap()); 24 | } 25 | -------------------------------------------------------------------------------- /src/play/players/player.rs: -------------------------------------------------------------------------------- 1 | use std::hash::Hash; 2 | 3 | /// A player who asks questions in the game. 4 | pub trait Questioner { 5 | type Error; 6 | 7 | /// Make a new question. 8 | fn make_new_question(&mut self); 9 | 10 | /// Answer for the question. 11 | fn answer(&self, answer: &[T]) -> Result<(usize, usize), Self::Error>; 12 | } 13 | 14 | /// A player who make guesses in the game. 15 | pub trait Guesser { 16 | type Error; 17 | 18 | /// Get guess times of this player. 19 | fn get_guess_times(&self) -> usize; 20 | 21 | /// Set guess times for this player. 22 | fn set_guess_times(&mut self, guess_times: usize); 23 | 24 | /// Add a condition. 25 | fn add_condition(&mut self, guess: &[T], reply: (usize, usize)); 26 | 27 | /// Make a guess. 28 | fn guess(&self) -> Result, Self::Error>; 29 | } 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 magiclen.org (Ron Li) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/parser/ab_parser.rs: -------------------------------------------------------------------------------- 1 | use regex::Regex; 2 | 3 | /// A parser to parse `XAYB` strings. 4 | #[derive(Debug)] 5 | pub struct ABParser { 6 | regex: Regex, 7 | } 8 | 9 | impl ABParser { 10 | /// Create a new `ABParser` instance. 11 | pub fn new() -> ABParser { 12 | let regex = Regex::new(r"^(\d+)[ ]*[aA][ ]*(\d+)[ ]*[bB]$").unwrap(); 13 | 14 | ABParser { 15 | regex, 16 | } 17 | } 18 | } 19 | 20 | impl ABParser { 21 | /// Parse a `XAYB` string in order to get the `X` and the `Y`. 22 | pub fn parse>(&self, s: S) -> Option<(usize, usize)> { 23 | let captures = self.regex.captures(s.as_ref().trim()); 24 | 25 | match captures { 26 | Some(captures) => { 27 | let a = match captures[1].parse::() { 28 | Ok(a) => a, 29 | Err(_) => return None, 30 | }; 31 | 32 | let b = match captures[2].parse::() { 33 | Ok(b) => b, 34 | Err(_) => return None, 35 | }; 36 | 37 | Some((a, b)) 38 | }, 39 | None => None, 40 | } 41 | } 42 | } 43 | 44 | impl Default for ABParser { 45 | #[inline] 46 | fn default() -> Self { 47 | ABParser::new() 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /.github/workflows/ci-version.yml: -------------------------------------------------------------------------------- 1 | name: CI-version 2 | 3 | on: 4 | push: 5 | tags: 6 | - "v*" 7 | 8 | env: 9 | CARGO_TERM_COLOR: always 10 | 11 | jobs: 12 | tests: 13 | strategy: 14 | fail-fast: false 15 | matrix: 16 | os: 17 | - ubuntu-latest 18 | - macos-latest 19 | - windows-latest 20 | toolchain: 21 | - stable 22 | - nightly 23 | features: 24 | - 25 | name: Test ${{ matrix.toolchain }} on ${{ matrix.os }} (${{ matrix.features }}) 26 | runs-on: ${{ matrix.os }} 27 | steps: 28 | - uses: actions/checkout@v6 29 | - uses: actions-rust-lang/setup-rust-toolchain@v1 30 | with: 31 | toolchain: ${{ matrix.toolchain }} 32 | - run: cargo test --release ${{ matrix.features }} 33 | - run: cargo doc --release ${{ matrix.features }} 34 | 35 | MSRV: 36 | strategy: 37 | fail-fast: false 38 | matrix: 39 | os: 40 | - ubuntu-latest 41 | - macos-latest 42 | - windows-latest 43 | toolchain: 44 | - "1.80" 45 | features: 46 | - 47 | name: Test ${{ matrix.toolchain }} on ${{ matrix.os }} (${{ matrix.features }}) 48 | runs-on: ${{ matrix.os }} 49 | steps: 50 | - uses: actions/checkout@v6 51 | - uses: actions-rust-lang/setup-rust-toolchain@v1 52 | with: 53 | toolchain: ${{ matrix.toolchain }} 54 | - run: cargo test --release --lib --bins ${{ matrix.features }} -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | /*! 2 | # Bulls and Cows 3 | 4 | This crate provides a framework for building bulls-and-cows games (1A2B) for any data type and any stages. 5 | 6 | Typically, Bulls and Cows is game that has 2 players, a questioner and a guesser. The questioner needs to decide a secret 4-digit (0 to 9) number in his or her mind and asks the guesser to guess the number. If the secret number is 4271 and the guess is 1234, then the questioner needs to answer `1A2B`. `1A2B` will be a new clue for the guesser to make the next guess better. 7 | 8 | Beside 4 digits, players can choose to play on any other length of digits. The 4-digit numbers can be changed to 4-letter words and the number of players can also be more than 2. 9 | 10 | ## Usage 11 | 12 | The `host` struct can be used independently for generating the question and answering for the question. 13 | 14 | ```rust 15 | let host = bulls_and_cows::Host::build_with_known_answer(bulls_and_cows::Letters::generate_numeric_letters(), vec![1, 2, 3, 4]).unwrap(); 16 | 17 | assert_eq!((4, 0), host.answer(&[1, 2, 3, 4]).unwrap()); 18 | assert_eq!((2, 2), host.answer(&[1, 2, 4, 3]).unwrap()); 19 | assert_eq!((0, 4), host.answer(&[4, 3, 2, 1]).unwrap()); 20 | assert_eq!((0, 0), host.answer(&[5, 6, 7, 8]).unwrap()); 21 | ``` 22 | 23 | If you want to build up a more complete game stage, use the `play` module. You can see the example `custom_2_players` to learn how to do that. 24 | */ 25 | 26 | mod host; 27 | mod letters; 28 | pub mod parser; 29 | pub mod play; 30 | 31 | pub use self::{ 32 | host::{Host, HostError}, 33 | letters::Letters, 34 | }; 35 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Bulls and Cows 2 | ==================== 3 | 4 | [![CI](https://github.com/magiclen/bulls-and-cows/actions/workflows/ci.yml/badge.svg)](https://github.com/magiclen/bulls-and-cows/actions/workflows/ci.yml) 5 | 6 | This crate provides a framework for building bulls-and-cows games (1A2B) for any data type and any stages. 7 | 8 | Typically, Bulls and Cows is a game that has 2 players, a questioner and a guesser. The questioner needs to decide a secret 4-digit (0 to 9) number in his or her mind and asks the guesser to guess the number. If the secret number is 4271 and the guess is 1234, then the questioner needs to answer `1A2B`. `1A2B` will be a new clue for the guesser to make the next guess better. 9 | 10 | Beside 4 digits, players can choose to play on any other length of digits. The 4-digit numbers can be changed to 4-letter words and the number of players can also be more than 2. 11 | 12 | ## Usage 13 | 14 | The `host` struct can be used independently for generating the question and answering for the question. 15 | 16 | ```rust 17 | let host = bulls_and_cows::Host::build_with_known_answer(bulls_and_cows::Letters::generate_numeric_letters(), vec![1, 2, 3, 4]).unwrap(); 18 | 19 | assert_eq!((4, 0), host.answer(&[1, 2, 3, 4]).unwrap()); 20 | assert_eq!((2, 2), host.answer(&[1, 2, 4, 3]).unwrap()); 21 | assert_eq!((0, 4), host.answer(&[4, 3, 2, 1]).unwrap()); 22 | assert_eq!((0, 0), host.answer(&[5, 6, 7, 8]).unwrap()); 23 | ``` 24 | 25 | If you want to build up a more complete game stage, use the `play` module. You can see the example `custom_2_players` to learn how to do that. 26 | 27 | ## Crates.io 28 | 29 | https://crates.io/crates/bulls-and-cows 30 | 31 | ## Documentation 32 | 33 | https://docs.rs/bulls-and-cows 34 | 35 | ## License 36 | 37 | [MIT](LICENSE) -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: [push, pull_request] 4 | 5 | env: 6 | CARGO_TERM_COLOR: always 7 | 8 | jobs: 9 | rustfmt: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v6 13 | - uses: actions-rust-lang/setup-rust-toolchain@v1 14 | with: 15 | toolchain: nightly 16 | components: rustfmt 17 | - uses: actions-rust-lang/rustfmt@v1 18 | 19 | clippy: 20 | runs-on: ubuntu-latest 21 | steps: 22 | - uses: actions/checkout@v6 23 | - uses: actions-rust-lang/setup-rust-toolchain@v1 24 | with: 25 | components: clippy 26 | - run: cargo clippy --all-targets --all-features -- -D warnings 27 | 28 | tests: 29 | strategy: 30 | fail-fast: false 31 | matrix: 32 | os: 33 | - ubuntu-latest 34 | - macos-latest 35 | - windows-latest 36 | toolchain: 37 | - stable 38 | - nightly 39 | features: 40 | - 41 | name: Test ${{ matrix.toolchain }} on ${{ matrix.os }} (${{ matrix.features }}) 42 | runs-on: ${{ matrix.os }} 43 | steps: 44 | - uses: actions/checkout@v6 45 | - uses: actions-rust-lang/setup-rust-toolchain@v1 46 | with: 47 | toolchain: ${{ matrix.toolchain }} 48 | - run: cargo test ${{ matrix.features }} 49 | - run: cargo doc ${{ matrix.features }} 50 | 51 | MSRV: 52 | strategy: 53 | fail-fast: false 54 | matrix: 55 | os: 56 | - ubuntu-latest 57 | - macos-latest 58 | - windows-latest 59 | toolchain: 60 | - "1.80" 61 | features: 62 | - 63 | name: Test ${{ matrix.toolchain }} on ${{ matrix.os }} (${{ matrix.features }}) 64 | runs-on: ${{ matrix.os }} 65 | steps: 66 | - uses: actions/checkout@v6 67 | - uses: actions-rust-lang/setup-rust-toolchain@v1 68 | with: 69 | toolchain: ${{ matrix.toolchain }} 70 | - run: cargo test --lib --bins ${{ matrix.features }} -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | # array_width = 60 2 | # attr_fn_like_width = 70 3 | binop_separator = "Front" 4 | blank_lines_lower_bound = 0 5 | blank_lines_upper_bound = 1 6 | brace_style = "PreferSameLine" 7 | # chain_width = 60 8 | color = "Auto" 9 | # comment_width = 100 10 | condense_wildcard_suffixes = true 11 | control_brace_style = "AlwaysSameLine" 12 | empty_item_single_line = true 13 | enum_discrim_align_threshold = 80 14 | error_on_line_overflow = false 15 | error_on_unformatted = false 16 | # fn_call_width = 60 17 | fn_params_layout = "Tall" 18 | fn_single_line = false 19 | force_explicit_abi = true 20 | force_multiline_blocks = false 21 | format_code_in_doc_comments = true 22 | doc_comment_code_block_width = 80 23 | format_generated_files = true 24 | format_macro_matchers = true 25 | format_macro_bodies = true 26 | skip_macro_invocations = [] 27 | format_strings = true 28 | hard_tabs = false 29 | hex_literal_case = "Upper" 30 | imports_indent = "Block" 31 | imports_layout = "Mixed" 32 | indent_style = "Block" 33 | inline_attribute_width = 0 34 | match_arm_blocks = true 35 | match_arm_leading_pipes = "Never" 36 | match_block_trailing_comma = true 37 | max_width = 100 38 | merge_derives = true 39 | imports_granularity = "Crate" 40 | newline_style = "Unix" 41 | normalize_comments = false 42 | normalize_doc_attributes = true 43 | overflow_delimited_expr = true 44 | remove_nested_parens = true 45 | reorder_impl_items = true 46 | reorder_imports = true 47 | group_imports = "StdExternalCrate" 48 | reorder_modules = true 49 | short_array_element_width_threshold = 10 50 | # single_line_if_else_max_width = 50 51 | space_after_colon = true 52 | space_before_colon = false 53 | spaces_around_ranges = false 54 | struct_field_align_threshold = 80 55 | struct_lit_single_line = false 56 | # struct_lit_width = 18 57 | # struct_variant_width = 35 58 | tab_spaces = 4 59 | trailing_comma = "Vertical" 60 | trailing_semicolon = true 61 | type_punctuation_density = "Wide" 62 | use_field_init_shorthand = true 63 | use_small_heuristics = "Max" 64 | use_try_shorthand = true 65 | where_single_line = false 66 | wrap_comments = false -------------------------------------------------------------------------------- /examples/4_digits.rs: -------------------------------------------------------------------------------- 1 | use std::io::{self, Write}; 2 | 3 | use bulls_and_cows::Host; 4 | 5 | const LETTER_LENGTH: usize = 4; // the reasonable range is 1~10 6 | 7 | fn main() { 8 | let mut host = Host::build_with_random_answer( 9 | bulls_and_cows::Letters::generate_numeric_letters(), 10 | LETTER_LENGTH, 11 | ) 12 | .expect("Failed to initialize a new game"); 13 | 14 | let mut guess = String::new(); 15 | 16 | loop { 17 | println!("A new question is done. The guesser can make a guess now."); 18 | 19 | loop { 20 | print!("> "); 21 | 22 | io::stdout().flush().expect("Failed to flush"); 23 | 24 | guess.clear(); 25 | 26 | io::stdin().read_line(&mut guess).expect("Failed to read line"); 27 | 28 | let guess = guess.trim(); 29 | 30 | if guess.len() != LETTER_LENGTH { 31 | println!("Wrong format!"); 32 | 33 | continue; 34 | } 35 | 36 | let mut answer = Vec::with_capacity(LETTER_LENGTH); 37 | 38 | for i in 0..LETTER_LENGTH { 39 | match guess.get(i..(i + 1)) { 40 | Some(o) => match o.parse() { 41 | Ok(n) => answer.push(n), 42 | Err(_) => { 43 | println!("Wrong format!"); 44 | 45 | continue; 46 | }, 47 | }, 48 | None => { 49 | println!("Wrong format!"); 50 | 51 | continue; 52 | }, 53 | } 54 | } 55 | 56 | match host.answer(&answer) { 57 | Ok((a, b)) => { 58 | println!("{}A{}B", a, b); 59 | 60 | if a == LETTER_LENGTH { 61 | println!("Congratulations!"); 62 | println!(); 63 | break; 64 | } 65 | }, 66 | Err(_) => { 67 | println!("Wrong format!"); 68 | 69 | continue; 70 | }, 71 | } 72 | } 73 | 74 | host.renew_with_random_answer(LETTER_LENGTH).expect("Failed to renew"); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ### Intellij+all ### 2 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider 3 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 4 | 5 | # User-specific stuff 6 | .idea/**/workspace.xml 7 | .idea/**/tasks.xml 8 | .idea/**/usage.statistics.xml 9 | .idea/**/dictionaries 10 | .idea/**/shelf 11 | 12 | # AWS User-specific 13 | .idea/**/aws.xml 14 | 15 | # Generated files 16 | .idea/**/contentModel.xml 17 | 18 | # Sensitive or high-churn files 19 | .idea/**/dataSources/ 20 | .idea/**/dataSources.ids 21 | .idea/**/dataSources.local.xml 22 | .idea/**/sqlDataSources.xml 23 | .idea/**/dynamic.xml 24 | .idea/**/uiDesigner.xml 25 | .idea/**/dbnavigator.xml 26 | 27 | # Gradle 28 | .idea/**/gradle.xml 29 | .idea/**/libraries 30 | 31 | # Gradle and Maven with auto-import 32 | # When using Gradle or Maven with auto-import, you should exclude module files, 33 | # since they will be recreated, and may cause churn. Uncomment if using 34 | # auto-import. 35 | # .idea/artifacts 36 | # .idea/compiler.xml 37 | # .idea/jarRepositories.xml 38 | # .idea/modules.xml 39 | # .idea/*.iml 40 | # .idea/modules 41 | # *.iml 42 | # *.ipr 43 | 44 | # CMake 45 | cmake-build-*/ 46 | 47 | # Mongo Explorer plugin 48 | .idea/**/mongoSettings.xml 49 | 50 | # File-based project format 51 | *.iws 52 | 53 | # IntelliJ 54 | out/ 55 | 56 | # mpeltonen/sbt-idea plugin 57 | .idea_modules/ 58 | 59 | # JIRA plugin 60 | atlassian-ide-plugin.xml 61 | 62 | # Cursive Clojure plugin 63 | .idea/replstate.xml 64 | 65 | # SonarLint plugin 66 | .idea/sonarlint/ 67 | 68 | # Crashlytics plugin (for Android Studio and IntelliJ) 69 | com_crashlytics_export_strings.xml 70 | crashlytics.properties 71 | crashlytics-build.properties 72 | fabric.properties 73 | 74 | # Editor-based Rest Client 75 | .idea/httpRequests 76 | 77 | # Android studio 3.1+ serialized cache file 78 | .idea/caches/build_file_checksums.ser 79 | 80 | ### Intellij+all Patch ### 81 | # Ignore everything but code style settings and run configurations 82 | # that are supposed to be shared within teams. 83 | 84 | .idea/* 85 | 86 | !.idea/codeStyles 87 | !.idea/runConfigurations 88 | 89 | ### Rust ### 90 | # Generated by Cargo 91 | # will have compiled files and executables 92 | debug/ 93 | target/ 94 | 95 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 96 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 97 | Cargo.lock 98 | 99 | # These are backup files generated by rustfmt 100 | **/*.rs.bk 101 | 102 | # MSVC Windows builds of rustc generate these, which store debugging information 103 | *.pdb 104 | 105 | ### Vim ### 106 | # Swap 107 | [._]*.s[a-v][a-z] 108 | !*.svg # comment out if you don't need vector files 109 | [._]*.sw[a-p] 110 | [._]s[a-rt-v][a-z] 111 | [._]ss[a-gi-z] 112 | [._]sw[a-p] 113 | 114 | # Session 115 | Session.vim 116 | Sessionx.vim 117 | 118 | # Temporary 119 | .netrwhist 120 | *~ 121 | # Auto-generated tag files 122 | tags 123 | # Persistent undo 124 | [._]*.un~ 125 | 126 | ### VisualStudioCode ### 127 | .vscode/* 128 | !.vscode/settings.json 129 | !.vscode/tasks.json 130 | !.vscode/launch.json 131 | !.vscode/extensions.json 132 | !.vscode/*.code-snippets 133 | 134 | # Local History for Visual Studio Code 135 | .history/ 136 | 137 | # Built Visual Studio Code Extensions 138 | *.vsix 139 | 140 | ### VisualStudioCode Patch ### 141 | # Ignore all local history of files 142 | .history 143 | .ionide -------------------------------------------------------------------------------- /src/play/players/computer.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | collections::HashSet, 3 | hash::Hash, 4 | thread, 5 | time::{Duration, Instant}, 6 | }; 7 | 8 | use permutohedron::Heap; 9 | use random_pick; 10 | 11 | use super::{Guesser, Questioner}; 12 | use crate::{Host, HostError}; 13 | 14 | /// A questioner controlled by a computer. 15 | #[derive(Debug)] 16 | pub struct ComputerQuestioner { 17 | host: Host, 18 | thinking_delay: u64, 19 | } 20 | 21 | impl ComputerQuestioner { 22 | /// Create a new computer player as a questioner. The `thinking_delay` is a value which simulates the time in milliseconds that a human player needs to take to think. 23 | pub fn new(host: Host, thinking_delay: u64) -> ComputerQuestioner { 24 | ComputerQuestioner { 25 | host, 26 | thinking_delay, 27 | } 28 | } 29 | } 30 | 31 | impl Questioner for ComputerQuestioner { 32 | type Error = HostError; 33 | 34 | fn make_new_question(&mut self) { 35 | thread::sleep(Duration::from_millis(self.thinking_delay)); 36 | 37 | let answer_length = self.host.get_answer_length(); 38 | 39 | self.host.renew_with_random_answer(answer_length).unwrap(); 40 | } 41 | 42 | fn answer(&self, answer: &[T]) -> Result<(usize, usize), Self::Error> { 43 | thread::sleep(Duration::from_millis(self.thinking_delay)); 44 | self.host.answer(answer) 45 | } 46 | } 47 | 48 | /// A guesser controlled by a computer. 49 | #[derive(Debug)] 50 | pub struct ComputerGuesser { 51 | letters: HashSet, 52 | letter_length: usize, 53 | possible_elements_table: Vec>, 54 | guess_times: usize, 55 | thinking_delay: u64, 56 | } 57 | 58 | impl ComputerGuesser { 59 | /// Create a new computer player as a guesser. The `thinking_delay` is a value which simulates the time in milliseconds that a human player needs to take to think. 60 | pub fn new(host: &Host, thinking_delay: u64) -> ComputerGuesser { 61 | let letters = host.get_letters().clone(); 62 | let letter_length = host.get_answer_length(); 63 | let possible_elements_table = 64 | ComputerGuesser::make_possible_elements_table(&letters, letter_length); 65 | 66 | ComputerGuesser { 67 | letters, 68 | letter_length, 69 | possible_elements_table, 70 | guess_times: 0, 71 | thinking_delay, 72 | } 73 | } 74 | 75 | fn make_possible_elements_table(letters: &HashSet, letter_length: usize) -> Vec> { 76 | let letters_length = letters.len(); 77 | 78 | let mut capacity = 1; 79 | 80 | for _ in 0..letter_length { 81 | capacity *= letters_length - letter_length; 82 | } 83 | 84 | let mut possible_elements_table = Vec::with_capacity(capacity); 85 | 86 | let letters_vec: Vec<&T> = letters.iter().collect(); 87 | 88 | let mut offset_array: Vec = Vec::with_capacity(letter_length); 89 | 90 | for i in 0..letter_length { 91 | offset_array.push(i); 92 | } 93 | 94 | 'outer: loop { 95 | let mut a: Vec<&T> = Vec::with_capacity(letter_length); 96 | 97 | for i in 0..letter_length { 98 | a.push(letters_vec[offset_array[i]]); 99 | } 100 | 101 | let heap = Heap::new(&mut a); 102 | 103 | for a in heap { 104 | possible_elements_table.push(a.iter().map(|&e| e.clone()).collect()); 105 | } 106 | 107 | let mut end = letter_length - 1; 108 | 109 | 'inner: loop { 110 | offset_array[end] += 1; 111 | 112 | if offset_array[end] >= letters_length { 113 | if end == 0 { 114 | break 'outer; 115 | } else { 116 | end -= 1; 117 | 118 | continue 'inner; 119 | } 120 | } else { 121 | for i in (end + 1)..letter_length { 122 | offset_array[i] = offset_array[i - 1] + 1; 123 | 124 | if offset_array[i] >= letters_length { 125 | if end == 0 { 126 | break 'outer; 127 | } else { 128 | end -= 1; 129 | 130 | continue 'inner; 131 | } 132 | } 133 | } 134 | break; 135 | } 136 | } 137 | } 138 | 139 | possible_elements_table 140 | } 141 | } 142 | 143 | impl Guesser for ComputerGuesser { 144 | type Error = HostError; 145 | 146 | fn get_guess_times(&self) -> usize { 147 | self.guess_times 148 | } 149 | 150 | fn set_guess_times(&mut self, guess_times: usize) { 151 | self.guess_times = guess_times 152 | } 153 | 154 | fn add_condition(&mut self, guess: &[T], reply: (usize, usize)) { 155 | let now = Instant::now(); 156 | 157 | let host = 158 | unsafe { Host::build_with_known_answer_unsafe(self.letters.clone(), guess.to_vec()) }; 159 | 160 | for i in (0..(self.possible_elements_table.len())).rev() { 161 | let re = { 162 | let answer = &self.possible_elements_table[i]; 163 | 164 | host.answer(answer).unwrap() 165 | }; 166 | 167 | if re != reply { 168 | self.possible_elements_table.remove(i); 169 | } 170 | } 171 | 172 | if self.possible_elements_table.is_empty() { 173 | self.possible_elements_table = 174 | ComputerGuesser::make_possible_elements_table(&self.letters, self.letter_length); 175 | } 176 | 177 | let dt = now.elapsed().as_millis(); 178 | 179 | if dt < u128::from(self.thinking_delay) { 180 | thread::sleep(Duration::from_millis(self.thinking_delay - dt as u64)); 181 | } 182 | } 183 | 184 | fn guess(&self) -> Result, Self::Error> { 185 | thread::sleep(Duration::from_millis(self.thinking_delay)); 186 | 187 | let picked = random_pick::pick_from_slice(&self.possible_elements_table, &[1]).unwrap(); 188 | 189 | Ok(picked.clone()) 190 | } 191 | } 192 | -------------------------------------------------------------------------------- /src/host.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | collections::HashSet, 3 | error::Error, 4 | fmt::{self, Debug, Display, Formatter}, 5 | hash::Hash, 6 | }; 7 | 8 | use rand::seq::SliceRandom; 9 | 10 | #[derive(PartialEq, Eq)] 11 | /// The possible errors for the `Host` struct. 12 | pub enum HostError { 13 | /// The length of letters for a Bulls and Cows game must be at least 1. 14 | LettersEmpty, 15 | /// The length of the answer is incorrect. 16 | AnswerLengthIncorrect, 17 | /// There is an incorrect letter in the answer. 18 | AnswerContainsIncorrectLetter(T), 19 | /// There is an duplicated letter in the answer. 20 | AnswerContainsDuplicatedLetter(T), 21 | } 22 | 23 | impl Debug for HostError { 24 | #[inline] 25 | fn fmt(&self, f: &mut Formatter) -> Result<(), fmt::Error> { 26 | debug_helper::impl_debug_for_enum!(HostError::{LettersEmpty, AnswerLengthIncorrect, (AnswerContainsIncorrectLetter(_): (let .0 = "AnswerContainsIncorrectLetter")), (AnswerContainsDuplicatedLetter(_): (let .0 = "AnswerContainsDuplicatedLetter"))}, f, self); 27 | } 28 | } 29 | 30 | impl Display for HostError { 31 | #[inline] 32 | fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), fmt::Error> { 33 | match self { 34 | HostError::LettersEmpty => { 35 | f.write_str("The length of letters for a Bulls and Cows game must be at least 1.") 36 | }, 37 | HostError::AnswerLengthIncorrect => { 38 | f.write_str("The length of the answer is incorrect.") 39 | }, 40 | HostError::AnswerContainsIncorrectLetter(_) => { 41 | f.write_str("There is an incorrect letter in the answer.") 42 | }, 43 | HostError::AnswerContainsDuplicatedLetter(_) => { 44 | f.write_str("There is an duplicated letter in the answer.") 45 | }, 46 | } 47 | } 48 | } 49 | 50 | impl Error for HostError {} 51 | 52 | /// The game host for generating the question and answering for the question. 53 | pub struct Host { 54 | letters: HashSet, 55 | answer: Vec, 56 | } 57 | 58 | impl Debug for Host { 59 | #[inline] 60 | fn fmt(&self, f: &mut Formatter) -> Result<(), fmt::Error> { 61 | debug_helper::impl_debug_for_struct!(Host, f, self, .letters, .answer); 62 | } 63 | } 64 | 65 | impl Host { 66 | pub fn get_letters(&self) -> &HashSet { 67 | &self.letters 68 | } 69 | 70 | pub fn get_answer_length(&self) -> usize { 71 | self.answer.len() 72 | } 73 | 74 | pub fn get_answer(&self) -> &[T] { 75 | &self.answer 76 | } 77 | } 78 | 79 | impl Host { 80 | /// Build a bulls-and-cows game host with a fixed answer. 81 | pub fn build(letters: HashSet, answer_length: usize) -> Result, HostError> { 82 | if letters.is_empty() { 83 | Err(HostError::LettersEmpty) 84 | } else { 85 | let letters_len = letters.len(); 86 | 87 | if answer_length == 0 || answer_length > letters_len { 88 | Err(HostError::AnswerLengthIncorrect) 89 | } else { 90 | let answer: Vec = letters.iter().take(answer_length).cloned().collect(); 91 | 92 | Ok(Host { 93 | letters, 94 | answer, 95 | }) 96 | } 97 | } 98 | } 99 | 100 | /// Build a bulls-and-cows game host with a random answer. 101 | pub fn build_with_random_answer( 102 | letters: HashSet, 103 | answer_length: usize, 104 | ) -> Result, HostError> { 105 | if letters.is_empty() { 106 | Err(HostError::LettersEmpty) 107 | } else { 108 | let mut host = Host { 109 | letters, 110 | answer: Vec::new(), 111 | }; 112 | 113 | host.renew_with_random_answer(answer_length)?; 114 | 115 | Ok(host) 116 | } 117 | } 118 | 119 | /// Build a bulls-and-cows game host with a known answer. 120 | pub fn build_with_known_answer( 121 | letters: HashSet, 122 | answer: Vec, 123 | ) -> Result, HostError> { 124 | if letters.is_empty() { 125 | Err(HostError::LettersEmpty) 126 | } else { 127 | let mut host = Host { 128 | letters, 129 | answer: Vec::new(), 130 | }; 131 | 132 | host.renew_with_known_answer(answer)?; 133 | 134 | Ok(host) 135 | } 136 | } 137 | 138 | #[allow(clippy::missing_safety_doc)] 139 | /// Build a bulls-and-cows game host with a known answer unsafely. 140 | pub unsafe fn build_with_known_answer_unsafe(letters: HashSet, answer: Vec) -> Host { 141 | Host { 142 | letters, 143 | answer, 144 | } 145 | } 146 | 147 | /// Renew this host with a random answer. 148 | pub fn renew_with_random_answer(&mut self, answer_length: usize) -> Result<(), HostError> { 149 | let letters = &self.letters; 150 | 151 | let letters_len = letters.len(); 152 | 153 | if answer_length == 0 || answer_length > letters_len { 154 | Err(HostError::AnswerLengthIncorrect) 155 | } else { 156 | let mut answer: Vec = Vec::with_capacity(answer_length); 157 | 158 | { 159 | let letters_vec: Vec<&T> = letters.iter().collect(); 160 | 161 | let mut indices: Vec = Vec::with_capacity(answer_length); 162 | 163 | for i in 0..answer_length { 164 | indices.push(i); 165 | } 166 | 167 | let mut rng = &mut rand::rng(); 168 | indices.shuffle(&mut rng); 169 | 170 | for i in indices { 171 | answer.push(letters_vec[i].clone()); 172 | } 173 | } 174 | 175 | self.answer = answer; 176 | 177 | Ok(()) 178 | } 179 | } 180 | 181 | /// Renew this host with a known answer. 182 | pub fn renew_with_known_answer(&mut self, answer: Vec) -> Result<(), HostError> { 183 | let letters = &self.letters; 184 | 185 | let letters_len = letters.len(); 186 | let answer_length = answer.len(); 187 | 188 | if answer_length == 0 || answer_length > letters_len { 189 | Err(HostError::AnswerLengthIncorrect) 190 | } else { 191 | let mut answer_2: Vec = Vec::with_capacity(answer_length); 192 | 193 | for letter in answer { 194 | if !letters.contains(&letter) { 195 | return Err(HostError::AnswerContainsIncorrectLetter(letter)); 196 | } 197 | if answer_2.contains(&letter) { 198 | return Err(HostError::AnswerContainsDuplicatedLetter(letter)); 199 | } 200 | 201 | answer_2.push(letter); 202 | } 203 | 204 | self.answer = answer_2; 205 | 206 | Ok(()) 207 | } 208 | } 209 | 210 | #[allow(clippy::missing_safety_doc)] 211 | /// Renew this host with a known answer unsafely. 212 | pub unsafe fn renew_with_known_answer_unsafe(&mut self, answer: Vec) { 213 | self.answer = answer; 214 | } 215 | } 216 | 217 | impl Host { 218 | /// Answer for the question. If the format of the input answer is correct, it returns the number of bulls and the number of cows. 219 | pub fn answer(&self, answer: &[T]) -> Result<(usize, usize), HostError> { 220 | let answer_length = answer.len(); 221 | 222 | if answer_length != self.get_answer_length() { 223 | Err(HostError::AnswerLengthIncorrect) 224 | } else { 225 | let mut answer_2: Vec<&T> = Vec::with_capacity(answer_length); 226 | 227 | let mut bulls = 0; 228 | let mut cows = 0; 229 | 230 | for (i, letter) in answer.iter().enumerate() { 231 | if !self.letters.contains(letter) { 232 | return Err(HostError::AnswerContainsIncorrectLetter(letter.clone())); 233 | } 234 | if answer_2.contains(&letter) { 235 | return Err(HostError::AnswerContainsDuplicatedLetter(letter.clone())); 236 | } 237 | 238 | if self.answer[i].eq(letter) { 239 | bulls += 1; 240 | } else if self.answer.contains(letter) { 241 | cows += 1; 242 | } 243 | 244 | answer_2.push(letter); 245 | } 246 | 247 | Ok((bulls, cows)) 248 | } 249 | } 250 | } 251 | -------------------------------------------------------------------------------- /examples/custom_2_players.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | io::{self, Write}, 3 | sync::LazyLock, 4 | }; 5 | 6 | use bulls_and_cows::{ 7 | parser::ABParser, 8 | play::players::{ComputerGuesser, ComputerQuestioner, Guesser, Questioner}, 9 | Host, HostError, 10 | }; 11 | 12 | const COM_THINKING_DELAY: u64 = 750; 13 | 14 | static AB_PARSER: LazyLock = LazyLock::new(ABParser::new); 15 | 16 | pub enum GameError { 17 | HostError(HostError), 18 | ABIncorrect((usize, usize)), 19 | ABError(String), 20 | GuessIncorrect, 21 | } 22 | 23 | pub struct CLIUserQuestioner { 24 | letter_length: usize, 25 | } 26 | 27 | impl CLIUserQuestioner { 28 | pub fn new(letter_length: usize) -> CLIUserQuestioner { 29 | CLIUserQuestioner { 30 | letter_length, 31 | } 32 | } 33 | } 34 | 35 | impl Questioner for CLIUserQuestioner { 36 | type Error = GameError; 37 | 38 | fn make_new_question(&mut self) { 39 | print!("Please make a question in your mind. Press Enter to continue..."); 40 | io::stdout().flush().unwrap(); 41 | io::stdin().read_line(&mut String::new()).expect("Failed to read line"); 42 | } 43 | 44 | fn answer(&self, _answer: &[u8]) -> Result<(usize, usize), Self::Error> { 45 | print!("> "); 46 | 47 | let mut reply = String::new(); 48 | 49 | io::stdout().flush().expect("Failed to flush"); 50 | 51 | io::stdin().read_line(&mut reply).expect("Failed to read line"); 52 | 53 | match AB_PARSER.parse(&reply) { 54 | Some((a, b)) => { 55 | if a + b > self.letter_length { 56 | Err(GameError::ABIncorrect((a, b))) 57 | } else { 58 | Ok((a, b)) 59 | } 60 | }, 61 | None => Err(GameError::ABError(reply)), 62 | } 63 | } 64 | } 65 | 66 | pub struct CLIUserGuesser { 67 | guess_times: usize, 68 | letter_length: usize, 69 | } 70 | 71 | impl CLIUserGuesser { 72 | pub fn new(letter_length: usize) -> CLIUserGuesser { 73 | CLIUserGuesser { 74 | guess_times: 0, 75 | letter_length, 76 | } 77 | } 78 | } 79 | 80 | impl Guesser for CLIUserGuesser { 81 | type Error = GameError; 82 | 83 | fn get_guess_times(&self) -> usize { 84 | self.guess_times 85 | } 86 | 87 | fn set_guess_times(&mut self, guess_times: usize) { 88 | self.guess_times = guess_times 89 | } 90 | 91 | fn add_condition(&mut self, _guess: &[u8], _reply: (usize, usize)) { 92 | // do nothing 93 | } 94 | 95 | fn guess(&self) -> Result, Self::Error> { 96 | let mut answer = Vec::with_capacity(self.letter_length); 97 | 98 | print!("> "); 99 | 100 | let mut guess = String::new(); 101 | 102 | io::stdout().flush().expect("Failed to flush"); 103 | 104 | io::stdin().read_line(&mut guess).expect("Failed to read line"); 105 | 106 | let guess = guess.trim(); 107 | 108 | if guess.len() != self.letter_length { 109 | return Err(GameError::GuessIncorrect); 110 | } 111 | 112 | for i in 0..self.letter_length { 113 | match guess.get(i..(i + 1)) { 114 | Some(o) => match o.parse() { 115 | Ok(n) => answer.push(n), 116 | Err(_) => { 117 | return Err(GameError::GuessIncorrect); 118 | }, 119 | }, 120 | None => { 121 | return Err(GameError::GuessIncorrect); 122 | }, 123 | } 124 | } 125 | 126 | Ok(answer) 127 | } 128 | } 129 | 130 | pub enum QuestioningPlayer { 131 | CLIUser(CLIUserQuestioner), 132 | Computer(ComputerQuestioner), 133 | } 134 | 135 | impl Questioner for QuestioningPlayer { 136 | type Error = GameError; 137 | 138 | fn make_new_question(&mut self) { 139 | match self { 140 | QuestioningPlayer::CLIUser(p) => p.make_new_question(), 141 | QuestioningPlayer::Computer(p) => p.make_new_question(), 142 | } 143 | } 144 | 145 | fn answer(&self, answer: &[u8]) -> Result<(usize, usize), Self::Error> { 146 | match self { 147 | QuestioningPlayer::CLIUser(p) => p.answer(answer), 148 | QuestioningPlayer::Computer(p) => p.answer(answer).map_err(GameError::HostError), 149 | } 150 | } 151 | } 152 | 153 | pub enum GuessingPlayer { 154 | CLIUser(CLIUserGuesser), 155 | Computer(ComputerGuesser), 156 | NotSet, 157 | } 158 | 159 | impl Guesser for GuessingPlayer { 160 | type Error = GameError; 161 | 162 | fn get_guess_times(&self) -> usize { 163 | match self { 164 | GuessingPlayer::CLIUser(p) => p.get_guess_times(), 165 | GuessingPlayer::Computer(p) => p.get_guess_times(), 166 | _ => unreachable!(), 167 | } 168 | } 169 | 170 | fn set_guess_times(&mut self, guess_times: usize) { 171 | match self { 172 | GuessingPlayer::CLIUser(p) => p.set_guess_times(guess_times), 173 | GuessingPlayer::Computer(p) => p.set_guess_times(guess_times), 174 | _ => unreachable!(), 175 | } 176 | } 177 | 178 | fn add_condition(&mut self, guess: &[u8], reply: (usize, usize)) { 179 | match self { 180 | GuessingPlayer::CLIUser(p) => p.add_condition(guess, reply), 181 | GuessingPlayer::Computer(p) => p.add_condition(guess, reply), 182 | _ => unreachable!(), 183 | } 184 | } 185 | 186 | fn guess(&self) -> Result, Self::Error> { 187 | match self { 188 | GuessingPlayer::CLIUser(p) => p.guess(), 189 | GuessingPlayer::Computer(p) => p.guess().map_err(GameError::HostError), 190 | _ => unreachable!(), 191 | } 192 | } 193 | } 194 | 195 | fn main() { 196 | let mut line = String::new(); 197 | 198 | loop { 199 | let letter_length: usize; 200 | 201 | loop { 202 | print!("Decide the digit length [1-10]: "); 203 | 204 | io::stdout().flush().expect("Failed to flush"); 205 | 206 | line.clear(); 207 | 208 | io::stdin().read_line(&mut line).expect("Failed to read line"); 209 | 210 | let line = line.trim(); 211 | 212 | letter_length = match line.parse() { 213 | Ok(letter_length) => { 214 | if !(1..=10).contains(&letter_length) { 215 | continue; 216 | } 217 | 218 | letter_length 219 | }, 220 | Err(_) => continue, 221 | }; 222 | 223 | break; 224 | } 225 | 226 | let host = Host::build(bulls_and_cows::Letters::generate_numeric_letters(), letter_length) 227 | .expect("Failed to initialize a new game"); 228 | 229 | let mut qp; 230 | let mut gp; 231 | 232 | loop { 233 | println!( 234 | "What are you want to be?\n 1. the questioner\n 2. the guesser\n 3. a \ 235 | spectator" 236 | ); 237 | 238 | print!("[1-3] "); 239 | 240 | io::stdout().flush().expect("Failed to flush"); 241 | 242 | line.clear(); 243 | 244 | io::stdin().read_line(&mut line).expect("Failed to read line"); 245 | 246 | let line = line.trim(); 247 | 248 | match line.parse() { 249 | Ok(line) => match line { 250 | 1 => { 251 | qp = QuestioningPlayer::CLIUser(CLIUserQuestioner::new(letter_length)); 252 | gp = GuessingPlayer::Computer(ComputerGuesser::new( 253 | &host, 254 | COM_THINKING_DELAY, 255 | )); 256 | }, 257 | 2 => { 258 | qp = QuestioningPlayer::Computer(ComputerQuestioner::new( 259 | host, 260 | COM_THINKING_DELAY, 261 | )); 262 | gp = GuessingPlayer::CLIUser(CLIUserGuesser::new(letter_length)); 263 | }, 264 | 3 => { 265 | gp = GuessingPlayer::Computer(ComputerGuesser::new( 266 | &host, 267 | COM_THINKING_DELAY, 268 | )); 269 | qp = QuestioningPlayer::Computer(ComputerQuestioner::new( 270 | host, 271 | COM_THINKING_DELAY, 272 | )); 273 | }, 274 | _ => continue, 275 | }, 276 | Err(_) => continue, 277 | } 278 | 279 | break; 280 | } 281 | 282 | qp.make_new_question(); 283 | 284 | println!("A new question is done. The guesser can guess now."); 285 | 286 | loop { 287 | let (guess, ab) = 'guess: loop { 288 | match gp.guess() { 289 | Ok(guess) => { 290 | let guess_times = gp.get_guess_times(); 291 | gp.set_guess_times(guess_times + 1); 292 | 293 | let mut s = String::with_capacity(letter_length); 294 | 295 | for e in guess.iter() { 296 | s.push((e + b'0') as char); 297 | } 298 | 299 | println!("Guesser: {}", s); 300 | 301 | io::stdout().flush().expect("Failed to flush"); 302 | 303 | break loop { 304 | match qp.answer(&guess) { 305 | Ok(ab) => break (guess, ab), 306 | Err(b) => { 307 | match b { 308 | GameError::HostError(_) => unreachable!(), 309 | GameError::GuessIncorrect => { 310 | println!("Questioner: Are you kidding?"); 311 | continue 'guess; 312 | }, 313 | GameError::ABIncorrect((a, b)) => { 314 | println!("Questioner: {}A{}B... just kidding.", a, b); 315 | }, 316 | GameError::ABError(s) => { 317 | println!("Questioner: {}... just kidding.", s.trim()); 318 | }, 319 | } 320 | 321 | continue; 322 | }, 323 | } 324 | }; 325 | }, 326 | Err(_) => { 327 | println!("Wrong format!"); 328 | 329 | continue; 330 | }, 331 | } 332 | }; 333 | 334 | println!("Questioner: {}A{}B", ab.0, ab.1); 335 | 336 | if ab.0 == letter_length { 337 | let times = gp.get_guess_times(); 338 | 339 | if times > 1 { 340 | println!("The guesser guessed {} times and finally wins!", times); 341 | } else { 342 | println!("The guesser guessed only one time and wins!"); 343 | } 344 | 345 | break; 346 | } 347 | 348 | gp.add_condition(&guess, ab) 349 | } 350 | } 351 | } 352 | --------------------------------------------------------------------------------