Skip to main content

saltine_caeser/
lib.rs

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}