Compare commits

...

3 Commits

Author SHA1 Message Date
b0ba9e63bd
Merge branch 'epic-64:main' into main 2025-11-29 11:08:57 +01:00
1b91536a5e
add FourTwosComplement Mode 2025-11-29 11:08:06 +01:00
William Raendchen
cb4ea13866
link readme CI status to main branch 2025-11-25 13:13:48 +01:00
4 changed files with 226 additions and 13 deletions

View File

@ -1,4 +1,4 @@
[![CI](https://github.com/epic-64/binbreak/workflows/CI/badge.svg)](https://github.com/epic-64/binbreak/actions) [![CI](https://github.com/epic-64/binbreak/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/epic-64/binbreak/actions)
[![Built With Ratatui](https://ratatui.rs/built-with-ratatui/badge.svg)](https://ratatui.rs/) [![Built With Ratatui](https://ratatui.rs/built-with-ratatui/badge.svg)](https://ratatui.rs/)
https://github.com/user-attachments/assets/4413fe8d-9a3f-4c00-9c1a-b9ca01a946fc https://github.com/user-attachments/assets/4413fe8d-9a3f-4c00-9c1a-b9ca01a946fc

164
main.diff Normal file
View File

@ -0,0 +1,164 @@
diff --git a/src/app.rs b/src/app.rs
index 8229747..a62f54e 100644
--- a/src/app.rs
+++ b/src/app.rs
@@ -264,6 +264,7 @@ impl StartMenuState {
fn with_selected(selected_index: usize) -> Self {
let items = vec![
("easy (4 bits)".to_string(), Bits::Four),
+ ("easy Two's complement (4 bits)".to_string(), Bits::FourTwosComplement),
("easy+16 (4 bits*16)".to_string(), Bits::FourShift4),
("easy+256 (4 bits*256)".to_string(), Bits::FourShift8),
("easy+4096 (4 bits*4096)".to_string(), Bits::FourShift12),
diff --git a/src/binary_numbers.rs b/src/binary_numbers.rs
index 7c3791b..21db1ce 100644
--- a/src/binary_numbers.rs
+++ b/src/binary_numbers.rs
@@ -190,7 +190,13 @@ impl BinaryNumbersPuzzle {
Block::bordered().border_type(border_type).fg(border_color).render(area, buf);
- let suggestion_str = format!("{suggestion}");
+ let suggestion_str = if self.bits.is_twos_complement() {
+ // Convert raw bit pattern to signed value for display
+ let signed_val = self.bits.raw_to_signed(*suggestion);
+ format!("{signed_val}")
+ } else {
+ format!("{suggestion}")
+ };
#[allow(clippy::cast_possible_truncation)]
Paragraph::new(suggestion_str.to_string())
@@ -642,6 +648,7 @@ enum GuessResult {
#[derive(Clone)]
pub enum Bits {
Four,
+ FourTwosComplement,
FourShift4,
FourShift8,
FourShift12,
@@ -654,6 +661,7 @@ impl Bits {
pub const fn to_int(&self) -> u32 {
match self {
Self::Four | Self::FourShift4 | Self::FourShift8 | Self::FourShift12 => 4,
+ Self::FourTwosComplement => 4,
Self::Eight => 8,
Self::Twelve => 12,
Self::Sixteen => 16,
@@ -662,6 +670,7 @@ impl Bits {
pub const fn scale_factor(&self) -> u32 {
match self {
Self::Four => 1,
+ Self::FourTwosComplement => 1,
Self::FourShift4 => 16,
Self::FourShift8 => 256,
Self::FourShift12 => 4096,
@@ -673,6 +682,7 @@ impl Bits {
pub const fn high_score_key(&self) -> u32 {
match self {
Self::Four => 4,
+ Self::FourTwosComplement => 42, // separate key for two's complement
Self::FourShift4 => 44,
Self::FourShift8 => 48,
Self::FourShift12 => 412,
@@ -686,7 +696,7 @@ impl Bits {
}
pub const fn suggestion_count(&self) -> usize {
match self {
- Self::Four | Self::FourShift4 | Self::FourShift8 | Self::FourShift12 => 3,
+ Self::Four | Self::FourShift4 | Self::FourShift8 | Self::FourShift12 | Self::FourTwosComplement => 3,
Self::Eight => 4,
Self::Twelve => 5,
Self::Sixteen => 6,
@@ -695,6 +705,7 @@ impl Bits {
pub const fn label(&self) -> &'static str {
match self {
Self::Four => "4 bits",
+ Self::FourTwosComplement => "4 bits (Two's complement)",
Self::FourShift4 => "4 bits*16",
Self::FourShift8 => "4 bits*256",
Self::FourShift12 => "4 bits*4096",
@@ -703,6 +714,25 @@ impl Bits {
Self::Sixteen => "16 bits",
}
}
+
+ /// Convert raw bit pattern to signed value for two's complement mode
+ pub const fn raw_to_signed(&self, raw: u32) -> i32 {
+ match self {
+ Self::FourTwosComplement => {
+ // 4-bit two's complement: range -8 to +7
+ if raw >= 8 {
+ (raw as i32) - 16
+ } else {
+ raw as i32
+ }
+ },
+ _ => raw as i32, // other modes use unsigned
+ }
+ }
+
+ pub const fn is_twos_complement(&self) -> bool {
+ matches!(self, Self::FourTwosComplement)
+ }
}
pub struct BinaryNumbersPuzzle {
@@ -725,21 +755,40 @@ impl BinaryNumbersPuzzle {
let mut suggestions = Vec::new();
let scale = bits.scale_factor();
- while suggestions.len() < bits.suggestion_count() {
- let raw = rng.random_range(0..u32::pow(2, bits.to_int()));
- let num = raw * scale;
- if !suggestions.contains(&num) {
- suggestions.push(num);
+
+ if bits.is_twos_complement() {
+ // For two's complement, generate unique raw bit patterns (0-15)
+ let mut raw_values: Vec<u32> = Vec::new();
+ while raw_values.len() < bits.suggestion_count() {
+ let raw = rng.random_range(0..u32::pow(2, bits.to_int()));
+ if !raw_values.contains(&raw) {
+ raw_values.push(raw);
+ }
+ }
+ // Store raw bit patterns directly
+ suggestions = raw_values;
+ } else {
+ // For unsigned modes
+ while suggestions.len() < bits.suggestion_count() {
+ let raw = rng.random_range(0..u32::pow(2, bits.to_int()));
+ let num = raw * scale;
+ if !suggestions.contains(&num) {
+ suggestions.push(num);
+ }
}
}
- let current_number = suggestions[0]; // scaled value
- let raw_current_number = current_number / scale; // back-calculate raw bits
+ let current_number = suggestions[0]; // scaled value or raw for twos complement
+ let raw_current_number = if bits.is_twos_complement() {
+ current_number // for two's complement, it's already the raw bit pattern
+ } else {
+ current_number / scale // back-calculate raw bits
+ };
suggestions.shuffle(&mut rng);
// Base time by bits + difficulty scaling (shorter as streak increases)
let base_time = match bits {
- Bits::Four | Bits::FourShift4 | Bits::FourShift8 | Bits::FourShift12 => 8.0,
+ Bits::Four | Bits::FourShift4 | Bits::FourShift8 | Bits::FourShift12 | Bits::FourTwosComplement => 8.0,
Bits::Eight => 12.0,
Bits::Twelve => 16.0,
Bits::Sixteen => 20.0,
@@ -868,7 +917,7 @@ impl HighScores {
fn save(&self) -> std::io::Result<()> {
let mut data = String::new();
- for key in [4u32, 44u32, 48u32, 412u32, 8u32, 12u32, 16u32] {
+ for key in [4u32, 42u32, 44u32, 48u32, 412u32, 8u32, 12u32, 16u32] {
let val = self.get(key);
let _ = writeln!(data, "{key}={val}");
}

View File

@ -264,6 +264,7 @@ impl StartMenuState {
fn with_selected(selected_index: usize) -> Self { fn with_selected(selected_index: usize) -> Self {
let items = vec![ let items = vec![
("easy (4 bits)".to_string(), Bits::Four), ("easy (4 bits)".to_string(), Bits::Four),
("easy Two's complement (4 bits)".to_string(), Bits::FourTwosComplement),
("easy+16 (4 bits*16)".to_string(), Bits::FourShift4), ("easy+16 (4 bits*16)".to_string(), Bits::FourShift4),
("easy+256 (4 bits*256)".to_string(), Bits::FourShift8), ("easy+256 (4 bits*256)".to_string(), Bits::FourShift8),
("easy+4096 (4 bits*4096)".to_string(), Bits::FourShift12), ("easy+4096 (4 bits*4096)".to_string(), Bits::FourShift12),

View File

@ -190,7 +190,13 @@ impl BinaryNumbersPuzzle {
Block::bordered().border_type(border_type).fg(border_color).render(area, buf); Block::bordered().border_type(border_type).fg(border_color).render(area, buf);
let suggestion_str = format!("{suggestion}"); let suggestion_str = if self.bits.is_twos_complement() {
// Convert raw bit pattern to signed value for display
let signed_val = self.bits.raw_to_signed(*suggestion);
format!("{signed_val}")
} else {
format!("{suggestion}")
};
#[allow(clippy::cast_possible_truncation)] #[allow(clippy::cast_possible_truncation)]
Paragraph::new(suggestion_str.to_string()) Paragraph::new(suggestion_str.to_string())
@ -642,6 +648,7 @@ enum GuessResult {
#[derive(Clone)] #[derive(Clone)]
pub enum Bits { pub enum Bits {
Four, Four,
FourTwosComplement,
FourShift4, FourShift4,
FourShift8, FourShift8,
FourShift12, FourShift12,
@ -653,7 +660,7 @@ pub enum Bits {
impl Bits { impl Bits {
pub const fn to_int(&self) -> u32 { pub const fn to_int(&self) -> u32 {
match self { match self {
Self::Four | Self::FourShift4 | Self::FourShift8 | Self::FourShift12 => 4, Self::Four | Self::FourShift4 | Self::FourShift8 | Self::FourShift12 | Self::FourTwosComplement=> 4,
Self::Eight => 8, Self::Eight => 8,
Self::Twelve => 12, Self::Twelve => 12,
Self::Sixteen => 16, Self::Sixteen => 16,
@ -662,6 +669,7 @@ impl Bits {
pub const fn scale_factor(&self) -> u32 { pub const fn scale_factor(&self) -> u32 {
match self { match self {
Self::Four => 1, Self::Four => 1,
Self::FourTwosComplement => 1,
Self::FourShift4 => 16, Self::FourShift4 => 16,
Self::FourShift8 => 256, Self::FourShift8 => 256,
Self::FourShift12 => 4096, Self::FourShift12 => 4096,
@ -673,6 +681,7 @@ impl Bits {
pub const fn high_score_key(&self) -> u32 { pub const fn high_score_key(&self) -> u32 {
match self { match self {
Self::Four => 4, Self::Four => 4,
Self::FourTwosComplement => 42, // separate key for two's complement
Self::FourShift4 => 44, Self::FourShift4 => 44,
Self::FourShift8 => 48, Self::FourShift8 => 48,
Self::FourShift12 => 412, Self::FourShift12 => 412,
@ -686,7 +695,7 @@ impl Bits {
} }
pub const fn suggestion_count(&self) -> usize { pub const fn suggestion_count(&self) -> usize {
match self { match self {
Self::Four | Self::FourShift4 | Self::FourShift8 | Self::FourShift12 => 3, Self::Four | Self::FourShift4 | Self::FourShift8 | Self::FourShift12 | Self::FourTwosComplement => 3,
Self::Eight => 4, Self::Eight => 4,
Self::Twelve => 5, Self::Twelve => 5,
Self::Sixteen => 6, Self::Sixteen => 6,
@ -695,6 +704,7 @@ impl Bits {
pub const fn label(&self) -> &'static str { pub const fn label(&self) -> &'static str {
match self { match self {
Self::Four => "4 bits", Self::Four => "4 bits",
Self::FourTwosComplement => "4 bits (Two's complement)",
Self::FourShift4 => "4 bits*16", Self::FourShift4 => "4 bits*16",
Self::FourShift8 => "4 bits*256", Self::FourShift8 => "4 bits*256",
Self::FourShift12 => "4 bits*4096", Self::FourShift12 => "4 bits*4096",
@ -703,6 +713,25 @@ impl Bits {
Self::Sixteen => "16 bits", Self::Sixteen => "16 bits",
} }
} }
/// Convert raw bit pattern to signed value for two's complement mode
pub const fn raw_to_signed(&self, raw: u32) -> i32 {
match self {
Self::FourTwosComplement => {
// 4-bit two's complement: range -8 to +7
if raw >= 8 {
(raw as i32) - 16
} else {
raw as i32
}
},
_ => raw as i32, // other modes use unsigned
}
}
pub const fn is_twos_complement(&self) -> bool {
matches!(self, Self::FourTwosComplement)
}
} }
pub struct BinaryNumbersPuzzle { pub struct BinaryNumbersPuzzle {
@ -725,21 +754,40 @@ impl BinaryNumbersPuzzle {
let mut suggestions = Vec::new(); let mut suggestions = Vec::new();
let scale = bits.scale_factor(); let scale = bits.scale_factor();
while suggestions.len() < bits.suggestion_count() {
let raw = rng.random_range(0..u32::pow(2, bits.to_int())); if bits.is_twos_complement() {
let num = raw * scale; // For two's complement, generate unique raw bit patterns (0-15)
if !suggestions.contains(&num) { let mut raw_values: Vec<u32> = Vec::new();
suggestions.push(num); while raw_values.len() < bits.suggestion_count() {
let raw = rng.random_range(0..u32::pow(2, bits.to_int()));
if !raw_values.contains(&raw) {
raw_values.push(raw);
}
}
// Store raw bit patterns directly
suggestions = raw_values;
} else {
// For unsigned modes
while suggestions.len() < bits.suggestion_count() {
let raw = rng.random_range(0..u32::pow(2, bits.to_int()));
let num = raw * scale;
if !suggestions.contains(&num) {
suggestions.push(num);
}
} }
} }
let current_number = suggestions[0]; // scaled value let current_number = suggestions[0]; // scaled value or raw for twos complement
let raw_current_number = current_number / scale; // back-calculate raw bits let raw_current_number = if bits.is_twos_complement() {
current_number // for two's complement, it's already the raw bit pattern
} else {
current_number / scale // back-calculate raw bits
};
suggestions.shuffle(&mut rng); suggestions.shuffle(&mut rng);
// Base time by bits + difficulty scaling (shorter as streak increases) // Base time by bits + difficulty scaling (shorter as streak increases)
let base_time = match bits { let base_time = match bits {
Bits::Four | Bits::FourShift4 | Bits::FourShift8 | Bits::FourShift12 => 8.0, Bits::Four | Bits::FourShift4 | Bits::FourShift8 | Bits::FourShift12 | Bits::FourTwosComplement => 8.0,
Bits::Eight => 12.0, Bits::Eight => 12.0,
Bits::Twelve => 16.0, Bits::Twelve => 16.0,
Bits::Sixteen => 20.0, Bits::Sixteen => 20.0,
@ -868,7 +916,7 @@ impl HighScores {
fn save(&self) -> std::io::Result<()> { fn save(&self) -> std::io::Result<()> {
let mut data = String::new(); let mut data = String::new();
for key in [4u32, 44u32, 48u32, 412u32, 8u32, 12u32, 16u32] { for key in [4u32, 42u32, 44u32, 48u32, 412u32, 8u32, 12u32, 16u32] {
let val = self.get(key); let val = self.get(key);
let _ = writeln!(data, "{key}={val}"); let _ = writeln!(data, "{key}={val}");
} }