commit 5e124728c690569cf45213179d8cdf6a29dd9890
parent 833b4ac8572d6b202b2cef563a6acc2b6ecaa79a
Author: Yuval Langer <yuval.langer@gmail.com>
Date: Mon, 4 Nov 2024 02:22:07 +0200
Rewrite the main function hopefully fix the asks too many dice rolls bug.
Diffstat:
M | src/main.rs | | | 314 | +++++++++++++++++++++++++++++++++++++++++-------------------------------------- |
1 file changed, 164 insertions(+), 150 deletions(-)
diff --git a/src/main.rs b/src/main.rs
@@ -14,22 +14,23 @@ GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
-*/
+ */
extern crate getopts;
extern crate rand;
use std::fs::File;
-use std::io::BufRead;
+// use std::io::BufRead;
use std::io::Read;
use std::process::exit;
use std::str::FromStr;
use diceware_wordlists::Wordlist;
use getopts::Options;
-use rand::prelude::SliceRandom;
+// use rand::prelude::SliceRandom;
use rand::rngs::ThreadRng;
use rand::thread_rng;
+use crate::rand::Rng;
fn make_options() -> Options {
let mut opts = Options::new();
@@ -66,126 +67,144 @@ fn unknown_wordlist(wordlist_name: &str) -> ! {
exit(-1)
}
-fn find_number_of_rolls_needed(wordlist_length: u32) -> u32 {
- let mut x = wordlist_length as u64;
- let mut nrolls: u32 = 0;
- while x != 0 {
- x /= 6;
- nrolls += 1;
- }
- nrolls
-}
-
-fn rolls_to_word_index(number_of_rolls_needed: u32, rolls: &[u8]) -> usize {
- if number_of_rolls_needed == rolls.len() as u32 {
- let mut word_number = 0;
- for (i, roll) in rolls.iter().rev().enumerate() {
- if *roll < 1 || *roll > 6 {
- panic!(
- "Must be a die roll result between and including 1 and 6, not {}",
- roll
- );
- }
- word_number += ((roll - 1) as usize) * 6_usize.pow(i as u32);
- }
- word_number
- } else {
- panic!(
- "Wrong number of die casts: {}. Needs to be {} die casts.",
- rolls.len(),
- number_of_rolls_needed
- )
- }
-}
-
-fn print_words_rolls(
- wordlist: &[&str],
- delimiter: &char,
- is_entropy_printed: &bool,
- rolls: &[&[u8]],
+fn print_words(
+ words: &Vec<String>,
+ delimiter: char,
) {
- let number_of_rolls_needed = find_number_of_rolls_needed(wordlist.len() as u32);
- for roll_index in 0..(rolls.len() - 1) {
- let roll_sum = rolls_to_word_index(
- number_of_rolls_needed,
- rolls
- .get(roll_index)
- .unwrap_or_else(|| panic!("Bad roll index: {}", roll_index)),
- );
- let word = wordlist
- .get(roll_sum)
- .unwrap_or_else(|| panic!("Wrong word index: {}", roll_sum));
- print!("{}{}", &word, delimiter);
+ for word in 0..(words.len() - 1) {
+ print!("{}{}", words[word], delimiter);
}
- let roll_sum = rolls_to_word_index(number_of_rolls_needed, rolls.last().unwrap());
- let word = wordlist.get(roll_sum).unwrap();
- print!("{}", &word);
+ print!("{}", words[words.len() - 1]);
println!();
- if *is_entropy_printed {
- println!("{}", entropyn(wordlist, rolls.len() as u64))
- }
}
-fn read_rolls() -> Vec<Vec<u8>> {
+fn read_single_word(bytes_iter: &mut std::iter::Peekable<std::io::Bytes<std::io::Stdin>>, wordlist: &Vec<&str>) -> Option<String> {
+ eprintln!("##### wordlist length: {}", wordlist.len());
+ let die_cast_per_word = die_cast_per_word(wordlist.len());
+ eprintln!("##### die cast per word: {}", die_cast_per_word);
+ let mut word_index = 0;
+
+ for die_significance in 0..=die_cast_per_word {
+ if die_significance == die_cast_per_word {
+ let current_byte_result = match bytes_iter.next() {
+ Some(x) => x,
+ None => {
+ eprintln!("Each roll must end with a newline.");
+ exit(-1);
+ }
+ };
+
+ match current_byte_result {
+ Ok(b'\n') => {
+ return Some(wordlist[word_index].to_string());
+ },
+ Ok(n) => match n {
+ b'1' | b'2' | b'3' | b'4' | b'5' | b'6' => {
+ eprintln!("Too many rolls. Each word must have exactly {} die rolls.",
+ die_cast_per_word);
+ exit(-1);
+ },
+ err => {
+ eprintln!("Input must be either 1 to 6 inclusive digits or newline. (Got: ascii code {})", err);
+ exit(-1);
+ },
+ },
+ Err(err) => {
+ eprintln!("Unknown error: {}", err);
+ exit(-1);
+ }
+ };
+ } else {
+ let current_byte_result = match bytes_iter.next() {
+ Some(x) => {
+ eprintln!("##### {:?}", x);
+ x
+ },
+ None => {
+ eprintln!("Bad number of rolls. Each word must have exactly {} die rolls.",
+ die_cast_per_word);
+ exit(-1);
+ },
+ };
+
+ word_index += match current_byte_result {
+ Ok(b'1') => 0 * 6_usize.pow(die_significance),
+ Ok(b'2') => 1 * 6_usize.pow(die_significance),
+ Ok(b'3') => 2 * 6_usize.pow(die_significance),
+ Ok(b'4') => 3 * 6_usize.pow(die_significance),
+ Ok(b'5') => 4 * 6_usize.pow(die_significance),
+ Ok(b'6') => 5 * 6_usize.pow(die_significance),
+ Ok(b'\n') => {
+ eprintln!("Bad number of rolls. Each word must have exactly {} die rolls.",
+ die_cast_per_word);
+ exit(-1);
+ },
+ Ok(x) => {
+ eprintln!("Input must be either 1 to 6 inclusive digits or newline. (Got ascii code {})", x);
+ exit(-1);
+ },
+ Err(err) => {
+ eprintln!("Error while reading die casts: {}", err);
+ exit(-1);
+ },
+ };
+ };
+ };
+
+ return Some(wordlist[word_index].to_string());
+}
+
+fn read_words_from_rolls(wordlist: &Vec<&str>) -> Vec<String> {
let stdin = std::io::stdin();
- let mut rolls: Vec<Vec<u8>> = Vec::new();
- let mut last_number_of_dice = None;
- for line in stdin.lock().lines() {
- let line_value = line.unwrap();
- let line_value_trimmed = line_value.trim();
- if line_value_trimmed.is_empty() {
- continue;
- }
- let current_number_of_dice = line_value_trimmed.len();
- if let Some(last_number_of_dice_value) = last_number_of_dice {
- if last_number_of_dice_value != current_number_of_dice {
- panic!("Not all dice rolls were of the same number of die.");
- } else {
- last_number_of_dice = Some(current_number_of_dice);
- }
+ let mut words: Vec<String> = Vec::new();
+ let mut bytes_iter = stdin.bytes().peekable();
+
+ loop {
+ match read_single_word(&mut bytes_iter, wordlist) {
+ Some(word) => words.push(word),
+ None => break,
+ };
+
+ match bytes_iter.peek() {
+ None => break,
+ Some(_) => continue,
}
+ };
- rolls.push(
- line_value_trimmed
- .chars()
- .map(|c| {
- c.to_string()
- .parse()
- .unwrap_or_else(|e| panic!("Not a digit: \"{}\". Error: {}", c, e))
- })
- .collect(),
- );
- }
- rolls
+ words
}
fn entropy(wordlist: &[&str]) -> f64 {
(wordlist.len() as f64).log2()
}
-fn entropyn(wordlist: &[&str], n: u64) -> f64 {
- entropy(wordlist) * (n as f64)
+fn entropyn(wordlist: Vec<&str>, n: u64) -> f64 {
+ entropy(&wordlist) * (n as f64)
}
-fn print_words_rng(
- wordlist: &[&str],
- word_num: &u64,
- delimiter: &char,
- is_entropy_printed: &bool,
+fn read_rng_rolls(
rng: &mut ThreadRng,
-) {
- for _ in 0..(word_num - 1) {
- let word = wordlist.choose(rng).unwrap();
- print!("{}{}", &word, delimiter);
- }
- let word = wordlist.choose(rng).unwrap();
- print!("{}", word);
-
- println!();
- if *is_entropy_printed {
- println!("{}", entropyn(wordlist, *word_num))
- }
+ wordlist: &Vec<&str>,
+ word_num: u64,
+) -> Vec<String> {
+ let die_cast_per_word = die_cast_per_word(wordlist.len());
+
+ /*
+ From base 6 conversion:
+ (0..6).pow(1) = (0, 1, ..6)
+ (0..6).pow(2) = (0, 6, ..36)
+ (0..6).pow(3) = (0, 36, ..216)
+ */
+ return (0..word_num)
+ .collect::<Vec<_>>()
+ .iter().map(|_| {
+ let current_word_index: usize = (0..die_cast_per_word).collect::<Vec<_>>()
+ .into_iter()
+ .map(|die_significance| (rng.gen_range(0..6_usize)) * ((6_usize).pow(die_significance)))
+ .sum();
+ wordlist[current_word_index].to_string()
+ }).collect();
}
fn load_wordlist_file(filepath: &str) -> String {
@@ -200,6 +219,10 @@ fn load_wordlist_file(filepath: &str) -> String {
wordlist_string
}
+fn die_cast_per_word(wordlist_length: usize) -> u32 {
+ wordlist_length.ilog(6)
+}
+
fn main() {
let args: Vec<String> = std::env::args().collect();
let program = &args[0];
@@ -241,54 +264,45 @@ fn main() {
Wordlist::default()
};
- let mut rng = thread_rng();
-
- if let Some(wordlist_filepath) = matches.opt_str("f") {
- let wordlist_string = load_wordlist_file(&wordlist_filepath);
- let wordlist = wordlist_string
+ let wordlist_string;
+
+ let wordlist = if let Some(wordlist_filepath) = matches.opt_str("f") {
+ wordlist_string = load_wordlist_file(&wordlist_filepath);
+ wordlist_string
.split('\n')
.map(|x| x.trim())
.filter(|x| x != &"")
- .collect::<Vec<&str>>();
-
- if is_physical_rolls {
- let rolls = read_rolls();
-
- print_words_rolls(
- &wordlist,
- &delimiter,
- &is_entropy_printed,
- &rolls.iter().map(|x| x.as_ref()).collect::<Vec<&[u8]>>(),
- );
- } else if word_num != 0 {
- print_words_rng(
- &wordlist,
- &word_num,
- &delimiter,
- &is_entropy_printed,
- &mut rng,
- );
- };
+ .collect::<Vec<&str>>()
} else {
- let wordlist = wordlist_name.get_list();
-
- if is_physical_rolls {
- let rolls = read_rolls();
-
- print_words_rolls(
- wordlist,
- &delimiter,
- &is_entropy_printed,
- &rolls.iter().map(|x| x.as_ref()).collect::<Vec<&[u8]>>(),
- );
- } else if word_num != 0 {
- print_words_rng(
- wordlist,
- &word_num,
- &delimiter,
- &is_entropy_printed,
- &mut rng,
- );
- };
+ wordlist_name.get_list().to_vec()
+ };
+
+ let words = if is_physical_rolls {
+ read_words_from_rolls(&wordlist)
+ } else if word_num != 0 {
+ let mut rng = thread_rng();
+ read_rng_rolls(&mut rng, &wordlist, word_num)
+ } else {
+ print_usage(program, opts);
+ exit(-1);
};
+
+ if words.len() > 0 {
+ print_words(&words, delimiter);
+ }
+
+ if is_entropy_printed {
+ println!("{}", entropyn(wordlist, words.len() as u64));
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn test_rolls_to_word_index_01() {
+ assert_eq!(0, rolls_to_word_index(3, &[1, 1, 1]));
+ assert_eq!(1, rolls_to_word_index(3, &[1, 1, 2]));
+ }
}