mirror of
https://github.com/ReVax-2007/cipher_master.git
synced 2026-09-17 21:32:27 +00:00
Compare commits
1 Commits
98917ccbf9
...
feature/fi
| Author | SHA1 | Date | |
|---|---|---|---|
| 4e24e2d989 |
37
.gitignore
vendored
37
.gitignore
vendored
@@ -1 +1,36 @@
|
|||||||
/target
|
# Rust and Tauri build output
|
||||||
|
/target/
|
||||||
|
/src-tauri/target/
|
||||||
|
|
||||||
|
# Operating-system metadata
|
||||||
|
.DS_Store
|
||||||
|
.AppleDouble
|
||||||
|
.LSOverride
|
||||||
|
._*
|
||||||
|
.Spotlight-V100/
|
||||||
|
.Trashes/
|
||||||
|
.fseventsd
|
||||||
|
.VolumeIcon.icns
|
||||||
|
Desktop.ini
|
||||||
|
Thumbs.db
|
||||||
|
ehthumbs.db
|
||||||
|
$RECYCLE.BIN/
|
||||||
|
|
||||||
|
# Editor and tool state
|
||||||
|
.idea/
|
||||||
|
*.swp
|
||||||
|
*.swo
|
||||||
|
*.swn
|
||||||
|
|
||||||
|
# Logs, temporary files, and local environment settings
|
||||||
|
*.log
|
||||||
|
*.tmp
|
||||||
|
*.temp
|
||||||
|
.env
|
||||||
|
.env.*
|
||||||
|
!.env.example
|
||||||
|
|
||||||
|
# Files generated by the application when run from the repository
|
||||||
|
cipher_challenge.txt
|
||||||
|
cipher_hint.txt
|
||||||
|
cipher_solution.txt
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ An AI-powered desktop application built with **Rust** and **Tauri v2** that gene
|
|||||||
|
|
||||||
## 🌟 Key Features
|
## 🌟 Key Features
|
||||||
|
|
||||||
* **8 Cipher Engines**:
|
* **9 Cipher Engines**:
|
||||||
* **Elder Futhark Runes**: Custom handwritten runic character map.
|
* **Elder Futhark Runes**: Custom handwritten runic character map.
|
||||||
* **Caesar Shift**: Dynamic rotational shift ($1..25$).
|
* **Caesar Shift**: Dynamic rotational shift ($1..25$).
|
||||||
* **Atbash Cipher**: Reversed alphabet substitution.
|
* **Atbash Cipher**: Reversed alphabet substitution.
|
||||||
@@ -22,6 +22,7 @@ An AI-powered desktop application built with **Rust** and **Tauri v2** that gene
|
|||||||
* **Rail Fence (Zig-Zag)**: Multi-rail transposition matrix.
|
* **Rail Fence (Zig-Zag)**: Multi-rail transposition matrix.
|
||||||
* **Affine Cipher**: Mathematical linear function cipher ($E(x) = (ax + b) \pmod{26}$).
|
* **Affine Cipher**: Mathematical linear function cipher ($E(x) = (ax + b) \pmod{26}$).
|
||||||
* **A1Z26 Numeric**: Positional index substitution.
|
* **A1Z26 Numeric**: Positional index substitution.
|
||||||
|
* **Fill in the Blank**: Assigns every letter a unique number from `01` to `26`, reveals a few mappings as clues, and preserves repeated-letter patterns.
|
||||||
* **Local AI Integration & Fallback Support**: Connects to a local [Ollama](https://ollama.com/) instance (`llama3.2:1b`) to generate dynamic quotes. If Ollama is not installed or running, the engine automatically uses internal text prompts.
|
* **Local AI Integration & Fallback Support**: Connects to a local [Ollama](https://ollama.com/) instance (`llama3.2:1b`) to generate dynamic quotes. If Ollama is not installed or running, the engine automatically uses internal text prompts.
|
||||||
* **Difficulty Scaling**:
|
* **Difficulty Scaling**:
|
||||||
* **Easy**: Preserves original word boundaries, spaces, and punctuation. Generates detailed letter frequency and vowel count hints.
|
* **Easy**: Preserves original word boundaries, spaces, and punctuation. Generates detailed letter frequency and vowel count hints.
|
||||||
@@ -31,6 +32,10 @@ An AI-powered desktop application built with **Rust** and **Tauri v2** that gene
|
|||||||
* `cipher_challenge.txt`: Formatted ciphertext ready to solve.
|
* `cipher_challenge.txt`: Formatted ciphertext ready to solve.
|
||||||
* `cipher_hint.txt`: Cryptanalytic structural metrics and riddle clues.
|
* `cipher_hint.txt`: Cryptanalytic structural metrics and riddle clues.
|
||||||
* `cipher_solution.txt`: Full original plaintext and decryption parameters/keys.
|
* `cipher_solution.txt`: Full original plaintext and decryption parameters/keys.
|
||||||
|
|
||||||
|
### Fill in the Blank Mode
|
||||||
|
|
||||||
|
This mode creates a numeric substitution puzzle. Each letter is assigned a unique number from `01` to `26`; the same letter always produces the same number, while different letters never share a number. Word boundaries remain visible in the challenge, and `cipher_hint.txt` reveals a small number of starter mappings based on the selected difficulty. The complete alphabet mapping is available only in `cipher_solution.txt`.
|
||||||
* **Cross-Platform Support**: Native desktop GUI built for macOS and Windows.
|
* **Cross-Platform Support**: Native desktop GUI built for macOS and Windows.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|||||||
1
SoT.md
1
SoT.md
@@ -125,6 +125,7 @@ $$E(x) = (a \cdot x + b) \pmod{26}$$
|
|||||||
|
|
||||||
|
|
||||||
8. **A1Z26 Numeric**: Converts each letter to its 1-based numerical index separated by hyphens (e.g., $A=1, B=2, Z=26$).
|
8. **A1Z26 Numeric**: Converts each letter to its 1-based numerical index separated by hyphens (e.g., $A=1, B=2, Z=26$).
|
||||||
|
9. **Fill in the Blank**: Builds a randomized one-to-one mapping from every letter in $A..Z$ to a unique number in $01..26$. The challenge displays those numbers while preserving word boundaries, `cipher_hint.txt` reveals a small set of known letter mappings, and `cipher_solution.txt` stores the complete mapping.
|
||||||
|
|
||||||
### B. Difficulty Formatting Pipeline
|
### B. Difficulty Formatting Pipeline
|
||||||
|
|
||||||
|
|||||||
@@ -58,6 +58,7 @@
|
|||||||
<option value="RailFence">Rail Fence (Zig-Zag)</option>
|
<option value="RailFence">Rail Fence (Zig-Zag)</option>
|
||||||
<option value="Affine">Affine Cipher</option>
|
<option value="Affine">Affine Cipher</option>
|
||||||
<option value="A1Z26">A1Z26 Numeric</option>
|
<option value="A1Z26">A1Z26 Numeric</option>
|
||||||
|
<option value="FillInTheBlank">Fill in the Blank (Number Mapping)</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ enum CipherType {
|
|||||||
RailFence,
|
RailFence,
|
||||||
Affine,
|
Affine,
|
||||||
A1Z26,
|
A1Z26,
|
||||||
|
FillInTheBlank,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
|
||||||
@@ -52,6 +53,7 @@ fn generate_cipher_challenge(
|
|||||||
"Vigenere" => CipherType::Vigenere,
|
"Vigenere" => CipherType::Vigenere,
|
||||||
"RailFence" => CipherType::RailFence,
|
"RailFence" => CipherType::RailFence,
|
||||||
"Affine" => CipherType::Affine,
|
"Affine" => CipherType::Affine,
|
||||||
|
"FillInTheBlank" => CipherType::FillInTheBlank,
|
||||||
_ => CipherType::A1Z26,
|
_ => CipherType::A1Z26,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -202,7 +204,7 @@ fn get_custom_futhark_map() -> HashMap<char, char> {
|
|||||||
fn encrypt(text: &str, cipher: CipherType, diff: Difficulty) -> (String, String, String) {
|
fn encrypt(text: &str, cipher: CipherType, diff: Difficulty) -> (String, String, String) {
|
||||||
let mut rng = rand::thread_rng();
|
let mut rng = rand::thread_rng();
|
||||||
|
|
||||||
let (ciphertext, key_info) = match cipher {
|
let (ciphertext, key_info, custom_hint) = match cipher {
|
||||||
CipherType::CustomFuthark => {
|
CipherType::CustomFuthark => {
|
||||||
let map = get_custom_futhark_map();
|
let map = get_custom_futhark_map();
|
||||||
let encrypted = text
|
let encrypted = text
|
||||||
@@ -210,7 +212,7 @@ fn encrypt(text: &str, cipher: CipherType, diff: Difficulty) -> (String, String,
|
|||||||
.chars()
|
.chars()
|
||||||
.map(|c| *map.get(&c).unwrap_or(&c))
|
.map(|c| *map.get(&c).unwrap_or(&c))
|
||||||
.collect();
|
.collect();
|
||||||
(encrypted, "Elder Futhark Key Map".to_string())
|
(encrypted, "Elder Futhark Key Map".to_string(), None)
|
||||||
}
|
}
|
||||||
CipherType::Caesar => {
|
CipherType::Caesar => {
|
||||||
let shift = rng.gen_range(1..25) as u8;
|
let shift = rng.gen_range(1..25) as u8;
|
||||||
@@ -223,7 +225,7 @@ fn encrypt(text: &str, cipher: CipherType, diff: Difficulty) -> (String, String,
|
|||||||
} else { c }
|
} else { c }
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
(encrypted, format!("Caesar Shift = {}", shift))
|
(encrypted, format!("Caesar Shift = {}", shift), None)
|
||||||
}
|
}
|
||||||
CipherType::Atbash => {
|
CipherType::Atbash => {
|
||||||
let encrypted = text
|
let encrypted = text
|
||||||
@@ -235,7 +237,7 @@ fn encrypt(text: &str, cipher: CipherType, diff: Difficulty) -> (String, String,
|
|||||||
} else { c }
|
} else { c }
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
(encrypted, "Atbash (Reversed Alphabet)".to_string())
|
(encrypted, "Atbash (Reversed Alphabet)".to_string(), None)
|
||||||
}
|
}
|
||||||
CipherType::Substitution => {
|
CipherType::Substitution => {
|
||||||
let mut alphabet: Vec<char> = "ABCDEFGHIJKLMNOPQRSTUVWXYZ".chars().collect();
|
let mut alphabet: Vec<char> = "ABCDEFGHIJKLMNOPQRSTUVWXYZ".chars().collect();
|
||||||
@@ -253,7 +255,7 @@ fn encrypt(text: &str, cipher: CipherType, diff: Difficulty) -> (String, String,
|
|||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
let key_str = map.iter().map(|(k, v)| format!("{}->{}", k, v)).collect::<Vec<String>>().join(", ");
|
let key_str = map.iter().map(|(k, v)| format!("{}->{}", k, v)).collect::<Vec<String>>().join(", ");
|
||||||
(encrypted, format!("Substitution Table: [{}]", key_str))
|
(encrypted, format!("Substitution Table: [{}]", key_str), None)
|
||||||
}
|
}
|
||||||
CipherType::Vigenere => {
|
CipherType::Vigenere => {
|
||||||
let keys = vec!["ODIN", "THOR", "VALHALLA", "RUNE", "RUST", "CRYPT"];
|
let keys = vec!["ODIN", "THOR", "VALHALLA", "RUNE", "RUST", "CRYPT"];
|
||||||
@@ -272,7 +274,7 @@ fn encrypt(text: &str, cipher: CipherType, diff: Difficulty) -> (String, String,
|
|||||||
} else { c }
|
} else { c }
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
(encrypted, format!("Vigenere Passphrase: {}", key))
|
(encrypted, format!("Vigenere Passphrase: {}", key), None)
|
||||||
}
|
}
|
||||||
CipherType::RailFence => {
|
CipherType::RailFence => {
|
||||||
let rails = rng.gen_range(2..=4);
|
let rails = rng.gen_range(2..=4);
|
||||||
@@ -288,7 +290,7 @@ fn encrypt(text: &str, cipher: CipherType, diff: Difficulty) -> (String, String,
|
|||||||
}
|
}
|
||||||
|
|
||||||
let encrypted = fence.into_iter().flatten().collect();
|
let encrypted = fence.into_iter().flatten().collect();
|
||||||
(encrypted, format!("Rail Fence Depth = {} Rails", rails))
|
(encrypted, format!("Rail Fence Depth = {} Rails", rails), None)
|
||||||
}
|
}
|
||||||
CipherType::Affine => {
|
CipherType::Affine => {
|
||||||
let a_options = vec![3, 5, 7, 9, 11, 15, 17, 19, 21, 23];
|
let a_options = vec![3, 5, 7, 9, 11, 15, 17, 19, 21, 23];
|
||||||
@@ -306,7 +308,7 @@ fn encrypt(text: &str, cipher: CipherType, diff: Difficulty) -> (String, String,
|
|||||||
} else { c }
|
} else { c }
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
(encrypted, format!("Affine Cipher: E(x) = ({}x + {}) mod 26", a, b))
|
(encrypted, format!("Affine Cipher: E(x) = ({}x + {}) mod 26", a, b), None)
|
||||||
}
|
}
|
||||||
CipherType::A1Z26 => {
|
CipherType::A1Z26 => {
|
||||||
let encrypted = text
|
let encrypted = text
|
||||||
@@ -322,14 +324,73 @@ fn encrypt(text: &str, cipher: CipherType, diff: Difficulty) -> (String, String,
|
|||||||
.collect::<String>()
|
.collect::<String>()
|
||||||
.trim_start_matches('-')
|
.trim_start_matches('-')
|
||||||
.to_string();
|
.to_string();
|
||||||
(encrypted, "A1Z26 Position Mapping".to_string())
|
(encrypted, "A1Z26 Position Mapping".to_string(), None)
|
||||||
|
}
|
||||||
|
CipherType::FillInTheBlank => {
|
||||||
|
let (challenge, key, hint) = generate_fill_in_the_blank(text, diff, &mut rng);
|
||||||
|
(challenge, key, Some(hint))
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let content_hint = generate_content_hints(text, diff);
|
let content_hint = custom_hint.unwrap_or_else(|| generate_content_hints(text, diff));
|
||||||
(ciphertext, key_info, content_hint)
|
(ciphertext, key_info, content_hint)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn generate_fill_in_the_blank(
|
||||||
|
text: &str,
|
||||||
|
diff: Difficulty,
|
||||||
|
rng: &mut impl Rng,
|
||||||
|
) -> (String, String, String) {
|
||||||
|
let alphabet: Vec<char> = "ABCDEFGHIJKLMNOPQRSTUVWXYZ".chars().collect();
|
||||||
|
let mut numbers: Vec<u8> = (1..=26).collect();
|
||||||
|
numbers.shuffle(rng);
|
||||||
|
let mapping: HashMap<char, u8> = alphabet.iter().copied().zip(numbers).collect();
|
||||||
|
|
||||||
|
let challenge = text
|
||||||
|
.chars()
|
||||||
|
.map(|character| {
|
||||||
|
if character.is_ascii_alphabetic() {
|
||||||
|
format!("{:02} ", mapping[&character.to_ascii_uppercase()])
|
||||||
|
} else if character.is_whitespace() {
|
||||||
|
" ".to_string()
|
||||||
|
} else {
|
||||||
|
character.to_string()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect::<String>()
|
||||||
|
.trim_end()
|
||||||
|
.to_string();
|
||||||
|
|
||||||
|
let used_letters: Vec<char> = alphabet
|
||||||
|
.iter()
|
||||||
|
.copied()
|
||||||
|
.filter(|letter| text.to_ascii_uppercase().contains(*letter))
|
||||||
|
.collect();
|
||||||
|
let clue_count = match diff {
|
||||||
|
Difficulty::Easy => 4,
|
||||||
|
Difficulty::Medium => 3,
|
||||||
|
Difficulty::Hard => 2,
|
||||||
|
}
|
||||||
|
.min(used_letters.len());
|
||||||
|
let clues = used_letters
|
||||||
|
.iter()
|
||||||
|
.take(clue_count)
|
||||||
|
.map(|letter| format!("{} = {:02}", letter, mapping[letter]))
|
||||||
|
.collect::<Vec<String>>();
|
||||||
|
|
||||||
|
let key = alphabet
|
||||||
|
.iter()
|
||||||
|
.map(|letter| format!("{}={:02}", letter, mapping[letter]))
|
||||||
|
.collect::<Vec<String>>()
|
||||||
|
.join(", ");
|
||||||
|
let hint = format!(
|
||||||
|
"FILL-IN-THE-BLANK RULES:\n- Each number represents one letter.\n- Every letter uses a unique number from 01 to 26.\n- Repeated numbers represent repeated letters.\n\nKNOWN LETTER MAPPINGS:\n{}",
|
||||||
|
clues.join(", ")
|
||||||
|
);
|
||||||
|
|
||||||
|
(challenge, format!("Fill-in-the-Blank Number Key: {}", key), hint)
|
||||||
|
}
|
||||||
|
|
||||||
fn generate_content_hints(plaintext: &str, diff: Difficulty) -> String {
|
fn generate_content_hints(plaintext: &str, diff: Difficulty) -> String {
|
||||||
let cleaned: Vec<char> = plaintext
|
let cleaned: Vec<char> = plaintext
|
||||||
.chars()
|
.chars()
|
||||||
@@ -404,6 +465,31 @@ fn generate_content_hints(plaintext: &str, diff: Difficulty) -> String {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn fill_in_the_blank_uses_unique_stable_numbers_and_clues() {
|
||||||
|
let (challenge, key, hint) = generate_fill_in_the_blank(
|
||||||
|
"ABBA CAB",
|
||||||
|
Difficulty::Easy,
|
||||||
|
&mut rand::thread_rng(),
|
||||||
|
);
|
||||||
|
|
||||||
|
let numbers: Vec<&str> = challenge
|
||||||
|
.split_whitespace()
|
||||||
|
.filter(|token| token.chars().all(|character| character.is_ascii_digit()))
|
||||||
|
.collect();
|
||||||
|
assert_eq!(numbers.len(), 7);
|
||||||
|
assert_eq!(numbers[1], numbers[2]);
|
||||||
|
assert_eq!(numbers[0], numbers[3]);
|
||||||
|
assert!(key.contains("A="));
|
||||||
|
assert!(key.contains("Z="));
|
||||||
|
assert!(hint.contains("KNOWN LETTER MAPPINGS:"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
||||||
pub fn run() {
|
pub fn run() {
|
||||||
tauri::Builder::default()
|
tauri::Builder::default()
|
||||||
|
|||||||
81
src/main.rs
81
src/main.rs
@@ -19,6 +19,7 @@ enum CipherType {
|
|||||||
RailFence,
|
RailFence,
|
||||||
Affine,
|
Affine,
|
||||||
A1Z26,
|
A1Z26,
|
||||||
|
FillInTheBlank,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
|
||||||
@@ -54,6 +55,7 @@ fn generate_cipher_challenge(
|
|||||||
"Vigenere" => CipherType::Vigenere,
|
"Vigenere" => CipherType::Vigenere,
|
||||||
"RailFence" => CipherType::RailFence,
|
"RailFence" => CipherType::RailFence,
|
||||||
"Affine" => CipherType::Affine,
|
"Affine" => CipherType::Affine,
|
||||||
|
"FillInTheBlank" => CipherType::FillInTheBlank,
|
||||||
_ => CipherType::A1Z26,
|
_ => CipherType::A1Z26,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -204,7 +206,7 @@ fn get_custom_futhark_map() -> HashMap<char, char> {
|
|||||||
fn encrypt(text: &str, cipher: CipherType, diff: Difficulty) -> (String, String, String) {
|
fn encrypt(text: &str, cipher: CipherType, diff: Difficulty) -> (String, String, String) {
|
||||||
let mut rng = rand::thread_rng();
|
let mut rng = rand::thread_rng();
|
||||||
|
|
||||||
let (ciphertext, key_info) = match cipher {
|
let (ciphertext, key_info, custom_hint) = match cipher {
|
||||||
CipherType::CustomFuthark => {
|
CipherType::CustomFuthark => {
|
||||||
let map = get_custom_futhark_map();
|
let map = get_custom_futhark_map();
|
||||||
let encrypted = text
|
let encrypted = text
|
||||||
@@ -212,7 +214,7 @@ fn encrypt(text: &str, cipher: CipherType, diff: Difficulty) -> (String, String,
|
|||||||
.chars()
|
.chars()
|
||||||
.map(|c| *map.get(&c).unwrap_or(&c))
|
.map(|c| *map.get(&c).unwrap_or(&c))
|
||||||
.collect();
|
.collect();
|
||||||
(encrypted, "Elder Futhark Key Map".to_string())
|
(encrypted, "Elder Futhark Key Map".to_string(), None)
|
||||||
}
|
}
|
||||||
CipherType::Caesar => {
|
CipherType::Caesar => {
|
||||||
let shift = rng.gen_range(1..25) as u8;
|
let shift = rng.gen_range(1..25) as u8;
|
||||||
@@ -225,7 +227,7 @@ fn encrypt(text: &str, cipher: CipherType, diff: Difficulty) -> (String, String,
|
|||||||
} else { c }
|
} else { c }
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
(encrypted, format!("Caesar Shift = {}", shift))
|
(encrypted, format!("Caesar Shift = {}", shift), None)
|
||||||
}
|
}
|
||||||
CipherType::Atbash => {
|
CipherType::Atbash => {
|
||||||
let encrypted = text
|
let encrypted = text
|
||||||
@@ -237,7 +239,7 @@ fn encrypt(text: &str, cipher: CipherType, diff: Difficulty) -> (String, String,
|
|||||||
} else { c }
|
} else { c }
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
(encrypted, "Atbash (Reversed Alphabet)".to_string())
|
(encrypted, "Atbash (Reversed Alphabet)".to_string(), None)
|
||||||
}
|
}
|
||||||
CipherType::Substitution => {
|
CipherType::Substitution => {
|
||||||
let mut alphabet: Vec<char> = "ABCDEFGHIJKLMNOPQRSTUVWXYZ".chars().collect();
|
let mut alphabet: Vec<char> = "ABCDEFGHIJKLMNOPQRSTUVWXYZ".chars().collect();
|
||||||
@@ -255,7 +257,7 @@ fn encrypt(text: &str, cipher: CipherType, diff: Difficulty) -> (String, String,
|
|||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
let key_str = map.iter().map(|(k, v)| format!("{}->{}", k, v)).collect::<Vec<String>>().join(", ");
|
let key_str = map.iter().map(|(k, v)| format!("{}->{}", k, v)).collect::<Vec<String>>().join(", ");
|
||||||
(encrypted, format!("Substitution Table: [{}]", key_str))
|
(encrypted, format!("Substitution Table: [{}]", key_str), None)
|
||||||
}
|
}
|
||||||
CipherType::Vigenere => {
|
CipherType::Vigenere => {
|
||||||
let keys = vec!["ODIN", "THOR", "VALHALLA", "RUNE", "RUST", "CRYPT"];
|
let keys = vec!["ODIN", "THOR", "VALHALLA", "RUNE", "RUST", "CRYPT"];
|
||||||
@@ -274,7 +276,7 @@ fn encrypt(text: &str, cipher: CipherType, diff: Difficulty) -> (String, String,
|
|||||||
} else { c }
|
} else { c }
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
(encrypted, format!("Vigenere Passphrase: {}", key))
|
(encrypted, format!("Vigenere Passphrase: {}", key), None)
|
||||||
}
|
}
|
||||||
CipherType::RailFence => {
|
CipherType::RailFence => {
|
||||||
let rails = rng.gen_range(2..=4);
|
let rails = rng.gen_range(2..=4);
|
||||||
@@ -290,7 +292,7 @@ fn encrypt(text: &str, cipher: CipherType, diff: Difficulty) -> (String, String,
|
|||||||
}
|
}
|
||||||
|
|
||||||
let encrypted = fence.into_iter().flatten().collect();
|
let encrypted = fence.into_iter().flatten().collect();
|
||||||
(encrypted, format!("Rail Fence Depth = {} Rails", rails))
|
(encrypted, format!("Rail Fence Depth = {} Rails", rails), None)
|
||||||
}
|
}
|
||||||
CipherType::Affine => {
|
CipherType::Affine => {
|
||||||
let a_options = vec![3, 5, 7, 9, 11, 15, 17, 19, 21, 23];
|
let a_options = vec![3, 5, 7, 9, 11, 15, 17, 19, 21, 23];
|
||||||
@@ -308,7 +310,7 @@ fn encrypt(text: &str, cipher: CipherType, diff: Difficulty) -> (String, String,
|
|||||||
} else { c }
|
} else { c }
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
(encrypted, format!("Affine Cipher: E(x) = ({}x + {}) mod 26", a, b))
|
(encrypted, format!("Affine Cipher: E(x) = ({}x + {}) mod 26", a, b), None)
|
||||||
}
|
}
|
||||||
CipherType::A1Z26 => {
|
CipherType::A1Z26 => {
|
||||||
let encrypted = text
|
let encrypted = text
|
||||||
@@ -324,14 +326,73 @@ fn encrypt(text: &str, cipher: CipherType, diff: Difficulty) -> (String, String,
|
|||||||
.collect::<String>()
|
.collect::<String>()
|
||||||
.trim_start_matches('-')
|
.trim_start_matches('-')
|
||||||
.to_string();
|
.to_string();
|
||||||
(encrypted, "A1Z26 Position Mapping".to_string())
|
(encrypted, "A1Z26 Position Mapping".to_string(), None)
|
||||||
|
}
|
||||||
|
CipherType::FillInTheBlank => {
|
||||||
|
let (challenge, key, hint) = generate_fill_in_the_blank(text, diff, &mut rng);
|
||||||
|
(challenge, key, Some(hint))
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let content_hint = generate_content_hints(text, diff);
|
let content_hint = custom_hint.unwrap_or_else(|| generate_content_hints(text, diff));
|
||||||
(ciphertext, key_info, content_hint)
|
(ciphertext, key_info, content_hint)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn generate_fill_in_the_blank(
|
||||||
|
text: &str,
|
||||||
|
diff: Difficulty,
|
||||||
|
rng: &mut impl Rng,
|
||||||
|
) -> (String, String, String) {
|
||||||
|
let alphabet: Vec<char> = "ABCDEFGHIJKLMNOPQRSTUVWXYZ".chars().collect();
|
||||||
|
let mut numbers: Vec<u8> = (1..=26).collect();
|
||||||
|
numbers.shuffle(rng);
|
||||||
|
let mapping: HashMap<char, u8> = alphabet.iter().copied().zip(numbers).collect();
|
||||||
|
|
||||||
|
let challenge = text
|
||||||
|
.chars()
|
||||||
|
.map(|character| {
|
||||||
|
if character.is_ascii_alphabetic() {
|
||||||
|
format!("{:02} ", mapping[&character.to_ascii_uppercase()])
|
||||||
|
} else if character.is_whitespace() {
|
||||||
|
" ".to_string()
|
||||||
|
} else {
|
||||||
|
character.to_string()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect::<String>()
|
||||||
|
.trim_end()
|
||||||
|
.to_string();
|
||||||
|
|
||||||
|
let used_letters: Vec<char> = alphabet
|
||||||
|
.iter()
|
||||||
|
.copied()
|
||||||
|
.filter(|letter| text.to_ascii_uppercase().contains(*letter))
|
||||||
|
.collect();
|
||||||
|
let clue_count = match diff {
|
||||||
|
Difficulty::Easy => 4,
|
||||||
|
Difficulty::Medium => 3,
|
||||||
|
Difficulty::Hard => 2,
|
||||||
|
}
|
||||||
|
.min(used_letters.len());
|
||||||
|
let clues = used_letters
|
||||||
|
.iter()
|
||||||
|
.take(clue_count)
|
||||||
|
.map(|letter| format!("{} = {:02}", letter, mapping[letter]))
|
||||||
|
.collect::<Vec<String>>();
|
||||||
|
|
||||||
|
let key = alphabet
|
||||||
|
.iter()
|
||||||
|
.map(|letter| format!("{}={:02}", letter, mapping[letter]))
|
||||||
|
.collect::<Vec<String>>()
|
||||||
|
.join(", ");
|
||||||
|
let hint = format!(
|
||||||
|
"FILL-IN-THE-BLANK RULES:\n- Each number represents one letter.\n- Every letter uses a unique number from 01 to 26.\n- Repeated numbers represent repeated letters.\n\nKNOWN LETTER MAPPINGS:\n{}",
|
||||||
|
clues.join(", ")
|
||||||
|
);
|
||||||
|
|
||||||
|
(challenge, format!("Fill-in-the-Blank Number Key: {}", key), hint)
|
||||||
|
}
|
||||||
|
|
||||||
fn generate_content_hints(plaintext: &str, diff: Difficulty) -> String {
|
fn generate_content_hints(plaintext: &str, diff: Difficulty) -> String {
|
||||||
let cleaned: Vec<char> = plaintext
|
let cleaned: Vec<char> = plaintext
|
||||||
.chars()
|
.chars()
|
||||||
|
|||||||
Reference in New Issue
Block a user