1use std::{convert::Infallible, fmt::Debug, marker::PhantomData};
2
3use bon::Builder;
4use saltine_core::{
5 Cipher,
6 CipherSolution,
7 CipherSolver,
8 alphabet::Alphabet,
9 analyze::TextLike,
10 character::Character,
11 language::{English, Language},
12};
13
14#[derive(Builder)]
15pub struct CaeserCipher<C: Character = char> {
16 #[builder(name = with_alphabet)]
17 alphabet: Alphabet<C>,
18 #[builder(name = with_shift)]
19 shift: usize,
20}
21
22impl CaeserCipher<char> {
23 pub fn new(shift: usize) -> Self {
24 CaeserCipher {
25 alphabet: English::alphabet(),
26 shift,
27 }
28 }
29}
30
31impl<C: Character> Cipher<C> for CaeserCipher<C> {
32 type DecryptionError = Infallible;
33
34 fn encrypt<T: TextLike<C> + ?Sized>(&self, plaintext: &T) -> impl TextLike<C> {
35 plaintext.characters().map(|letter| self.alphabet.shift_char(letter, self.shift)).collect::<Vec<_>>()
36 }
37
38 fn decrypt<T: TextLike<C> + ?Sized>(&self, ciphertext: &T) -> Result<impl TextLike<C>, Self::DecryptionError> {
39 Ok(ciphertext
40 .characters()
41 .map(|letter| self.alphabet.shift_char(letter, self.alphabet.len() - self.shift))
42 .collect::<Vec<_>>())
43 }
44}
45
46#[derive(Builder, Debug)]
47pub struct CaeserCipherSolver<C: Character, L: Language<Character = C>> {
48 #[builder(name = with_alphabet)]
49 alphabet: Alphabet<C>,
50
51 _phantom: PhantomData<L>,
52}
53
54impl CaeserCipherSolver<char, English> {
55 pub fn new() -> CaeserCipherSolver<char, English> {
56 CaeserCipherSolver::<char, English> {
57 alphabet: English::alphabet(),
58 _phantom: PhantomData,
59 }
60 }
61}
62
63impl<L: Language> CipherSolver<L> for CaeserCipherSolver<L::Character, L> {
64 type Solution = CaeserCipherSolution<L::Character>;
65
66 fn crack<T: TextLike<L::Character> + ?Sized>(&self, ciphertext: &T) -> CipherSolution<CaeserCipherSolution<L::Character>> {
67 (0..self.alphabet.len())
68 .into_iter()
69 .map(|shift| {
70 let solver = CaeserCipher::builder().with_alphabet(self.alphabet.clone()).with_shift(shift).build();
71 let plaintext = solver.decrypt(ciphertext).unwrap();
72 let score = plaintext.chance_of_being::<L>();
73 CipherSolution::<L::Character>::new::<CaeserCipherSolution<L::Character>>(
74 CaeserCipherSolution {
75 plaintext: plaintext.to::<Vec<_>>(),
76 shift,
77 },
78 score,
79 )
80 })
81 .max_by(|s1, s2| s1.score().partial_cmp(&s2.score()).unwrap_or(std::cmp::Ordering::Equal))
82 .unwrap()
83 }
84}
85
86#[derive(Debug)]
87pub struct CaeserCipherSolution<C> {
88 plaintext: Vec<C>,
89 shift: usize,
90}
91
92impl<C: Character> CaeserCipherSolution<C> {
93 pub fn plaintext(&self) -> &impl TextLike<C> {
94 &self.plaintext
95 }
96
97 pub fn shift(&self) -> usize {
98 self.shift
99 }
100}