From cdd07a6928f3311fd44021a9dd1a25e5158cd51c Mon Sep 17 00:00:00 2001 From: epic-64 Date: Wed, 3 Dec 2025 00:00:00 +0100 Subject: [PATCH 1/6] feat: randomize suggestions and store correct answer in BinaryNumbersPuzzle --- src/binary_numbers.rs | 52 ++++++++++++++++++++++++++++++++++++++----- 1 file changed, 47 insertions(+), 5 deletions(-) diff --git a/src/binary_numbers.rs b/src/binary_numbers.rs index a25d483..fa41b10 100644 --- a/src/binary_numbers.rs +++ b/src/binary_numbers.rs @@ -729,6 +729,7 @@ pub struct BinaryNumbersPuzzle { current_number: u32, // scaled value used for suggestions matching raw_current_number: u32, // raw bit value (unscaled) for display suggestions: Vec, // Changed to i32 to support signed values + correct_answer: i32, // The correct answer (one of the suggestions) selected_suggestion: Option, time_total: f64, time_left: f64, @@ -777,11 +778,13 @@ impl BinaryNumbersPuzzle { }, } - // Shuffle suggestions + // Pick a random suggestion as the current number + let correct_index = rng.random_range(0..suggestions.len()); + let current_number_signed = suggestions[correct_index]; + + // Shuffle suggestions so the correct answer is in a random position suggestions.shuffle(&mut rng); - // Pick first suggestion as the current number - let current_number_signed = suggestions[0]; // Calculate raw_current_number based on mode let raw_current_number = match number_mode { @@ -817,6 +820,7 @@ impl BinaryNumbersPuzzle { current_number, raw_current_number, suggestions, + correct_answer: current_number_signed, time_total, time_left, selected_suggestion, @@ -832,7 +836,7 @@ impl BinaryNumbersPuzzle { } pub fn is_correct_guess(&self, guess: i32) -> bool { - guess == self.suggestions[0] + guess == self.correct_answer } pub fn current_to_binary_string(&self) -> String { @@ -1045,7 +1049,7 @@ mod tests { // the raw_current_number has the sign bit set correctly for _ in 0..20 { let p = BinaryNumbersPuzzle::new(Bits::Four, NumberMode::Signed, 0); - let current_signed = p.suggestions[0]; + let current_signed = p.correct_answer; if current_signed < 0 { // For negative numbers in 4-bit two's complement, the MSB (bit 3) should be 1 @@ -1084,6 +1088,44 @@ mod tests { assert_eq!(p.guess_result, Some(GuessResult::Timeout)); } + #[test] + fn suggestions_are_randomized() { + // Verify that suggestions are properly randomized and the first suggestion + // is not always the correct answer + let mut first_is_correct_count = 0; + let trials = 100; + + for _ in 0..trials { + let p = BinaryNumbersPuzzle::new(Bits::Four, NumberMode::Unsigned, 0); + // Check if the first suggestion happens to be the correct answer + if p.suggestions[0] == p.correct_answer { + first_is_correct_count += 1; + } + } + + // With 3 suggestions for 4-bit mode, we expect roughly 33% to be correct by chance + // Allow a range of 20-50% (which is generous for 100 trials to account for randomness) + // The key point is that it's NOT 100% (which would indicate no randomization) + assert!( + first_is_correct_count >= 20 && first_is_correct_count <= 50, + "First suggestion was correct {} times out of {}, expected around 33% (20-50 range). \ + If this is close to 100%, suggestions are not randomized!", + first_is_correct_count, + trials + ); + + // Also verify that the correct answer is actually one of the suggestions + for _ in 0..10 { + let p = BinaryNumbersPuzzle::new(Bits::Eight, NumberMode::Signed, 0); + assert!( + p.suggestions.contains(&p.correct_answer), + "correct_answer {} must be in suggestions {:?}", + p.correct_answer, + p.suggestions + ); + } + } + #[test] fn finalize_round_correct_increments_score_streak_and_sets_result_state() { with_high_score_file(|| { From 3cad026a795041ac9c20bfee1e63a760f0e496da Mon Sep 17 00:00:00 2001 From: epic-64 Date: Wed, 3 Dec 2025 00:00:00 +0100 Subject: [PATCH 2/6] feat: remove unused fields and update assertions in BinaryNumbersPuzzle --- src/binary_numbers.rs | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/src/binary_numbers.rs b/src/binary_numbers.rs index fa41b10..2ed3c2d 100644 --- a/src/binary_numbers.rs +++ b/src/binary_numbers.rs @@ -723,10 +723,6 @@ impl Bits { pub struct BinaryNumbersPuzzle { bits: Bits, - #[allow(dead_code)] - number_mode: NumberMode, - #[allow(dead_code)] - current_number: u32, // scaled value used for suggestions matching raw_current_number: u32, // raw bit value (unscaled) for display suggestions: Vec, // Changed to i32 to support signed values correct_answer: i32, // The correct answer (one of the suggestions) @@ -804,8 +800,6 @@ impl BinaryNumbersPuzzle { }, }; - let current_number = current_number_signed.unsigned_abs(); - // Calculate time based on difficulty let time_total = 10.0 - (streak.min(8) as f64 * 0.5); let time_left = time_total; @@ -816,8 +810,6 @@ impl BinaryNumbersPuzzle { Self { bits, - number_mode, - current_number, raw_current_number, suggestions, correct_answer: current_number_signed, @@ -1005,9 +997,9 @@ mod tests { for &s in p.suggestions() { assert_eq!(s.unsigned_abs() % scale, 0); } - // current number must be one of suggestions and raw_current_number * scale == current_number - assert!(p.suggestions().contains(&(p.current_number as i32))); - assert_eq!(p.raw_current_number * scale, p.current_number); + // correct answer must be one of suggestions and raw_current_number * scale == correct_answer (unsigned) + assert!(p.suggestions().contains(&p.correct_answer)); + assert_eq!(p.raw_current_number * scale, p.correct_answer.unsigned_abs()); } #[test] @@ -1131,7 +1123,7 @@ mod tests { with_high_score_file(|| { let mut g = BinaryNumbersGame::new(Bits::Four, NumberMode::Unsigned); // ensure deterministic: mark puzzle correct - let answer = g.puzzle.current_number as i32; + let answer = g.puzzle.correct_answer; g.puzzle.guess_result = Some(GuessResult::Correct); g.finalize_round(); assert_eq!(g.streak, 1); From 236179f8e2da71c547f8cdbe9c87547deae8557c Mon Sep 17 00:00:00 2001 From: epic-64 Date: Wed, 3 Dec 2025 00:00:00 +0100 Subject: [PATCH 3/6] feat: update documentation for raw_current_number and clarify signed mode handling --- src/binary_numbers.rs | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/binary_numbers.rs b/src/binary_numbers.rs index 2ed3c2d..14f9fa5 100644 --- a/src/binary_numbers.rs +++ b/src/binary_numbers.rs @@ -723,7 +723,13 @@ impl Bits { pub struct BinaryNumbersPuzzle { bits: Bits, - raw_current_number: u32, // raw bit value (unscaled) for display + /// Raw bit pattern (unscaled) for display as binary string. + /// This is u32 (not i32) because it stores the BIT PATTERN, not the numeric value. + /// In signed mode, negative numbers use two's complement representation: + /// - For -1 in 4-bit: raw_current_number = 15 (0b1111), displayed as "1111" + /// - For -8 in 4-bit: raw_current_number = 8 (0b1000), displayed as "1000" + /// The same bit pattern has different meanings in signed vs unsigned mode. + raw_current_number: u32, suggestions: Vec, // Changed to i32 to support signed values correct_answer: i32, // The correct answer (one of the suggestions) selected_suggestion: Option, @@ -790,10 +796,13 @@ impl BinaryNumbersPuzzle { }, NumberMode::Signed => { // For signed mode, we need to preserve the two's complement representation - // First, get the unscaled signed value + // Example: -1 in 4-bit two's complement is 0b1111 + // We cast i32 to u32 (preserving bit pattern), then mask to n-bits + // Result: -1 becomes 15u32 (0b1111), which displays as "1111" let unscaled_signed = current_number_signed / (scale as i32); // Convert to unsigned bits using two's complement masking + // Casting i32 to u32 reinterprets the bits (not a numeric conversion) // For n-bit number, mask is (2^n - 1) let mask = (1u32 << num_bits) - 1; (unscaled_signed as u32) & mask From 0341d8c896ac0813a953daad8d80c346a2d2de92 Mon Sep 17 00:00:00 2001 From: epic-64 Date: Wed, 3 Dec 2025 00:00:00 +0100 Subject: [PATCH 4/6] cleanup --- src/binary_numbers.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/binary_numbers.rs b/src/binary_numbers.rs index 14f9fa5..985b06e 100644 --- a/src/binary_numbers.rs +++ b/src/binary_numbers.rs @@ -730,8 +730,8 @@ pub struct BinaryNumbersPuzzle { /// - For -8 in 4-bit: raw_current_number = 8 (0b1000), displayed as "1000" /// The same bit pattern has different meanings in signed vs unsigned mode. raw_current_number: u32, - suggestions: Vec, // Changed to i32 to support signed values - correct_answer: i32, // The correct answer (one of the suggestions) + suggestions: Vec, + correct_answer: i32, selected_suggestion: Option, time_total: f64, time_left: f64, @@ -787,7 +787,6 @@ impl BinaryNumbersPuzzle { // Shuffle suggestions so the correct answer is in a random position suggestions.shuffle(&mut rng); - // Calculate raw_current_number based on mode let raw_current_number = match number_mode { NumberMode::Unsigned => { From 300d106e3b3c9d149fac78e22113ab2f2536a968 Mon Sep 17 00:00:00 2001 From: epic-64 Date: Wed, 3 Dec 2025 00:00:00 +0100 Subject: [PATCH 5/6] cleanup --- src/binary_numbers.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/binary_numbers.rs b/src/binary_numbers.rs index 985b06e..af689a9 100644 --- a/src/binary_numbers.rs +++ b/src/binary_numbers.rs @@ -728,6 +728,7 @@ pub struct BinaryNumbersPuzzle { /// In signed mode, negative numbers use two's complement representation: /// - For -1 in 4-bit: raw_current_number = 15 (0b1111), displayed as "1111" /// - For -8 in 4-bit: raw_current_number = 8 (0b1000), displayed as "1000" + /// /// The same bit pattern has different meanings in signed vs unsigned mode. raw_current_number: u32, suggestions: Vec, From bc053b48cd91e6105c4925a4142ad602662af20c Mon Sep 17 00:00:00 2001 From: epic-64 Date: Wed, 3 Dec 2025 00:00:00 +0100 Subject: [PATCH 6/6] cleanup --- src/binary_numbers.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/binary_numbers.rs b/src/binary_numbers.rs index af689a9..a13e3c6 100644 --- a/src/binary_numbers.rs +++ b/src/binary_numbers.rs @@ -728,7 +728,7 @@ pub struct BinaryNumbersPuzzle { /// In signed mode, negative numbers use two's complement representation: /// - For -1 in 4-bit: raw_current_number = 15 (0b1111), displayed as "1111" /// - For -8 in 4-bit: raw_current_number = 8 (0b1000), displayed as "1000" - /// + /// /// The same bit pattern has different meanings in signed vs unsigned mode. raw_current_number: u32, suggestions: Vec,