OBZ6CRK6CFPFTVNYTFGCVFATVVIPWY3UZKDYVDRGVNRVRMZVM7MAC
#[derive(Clone, Copy)]
pub struct InternalLetter(u8);
impl AsRef<u8> for InternalLetter {
fn as_ref(&self) -> &u8 {
&self.0
}
}
impl From<InternalLetter> for u8 {
fn from(l: InternalLetter) -> Self {
l.0
}
}
impl From<InternalLetter> for String {
fn from(l: InternalLetter) -> Self {
let u : u8 = l.into();
let s: String = std::str::from_utf8(&[u]).expect("InternalLetters are always valid utf-8").to_string();
s
}
}
impl TryFrom<char> for InternalLetter {
type Error = HangmanError;
fn try_from(value: char) -> Result<Self, Self::Error> {
let mut buf: [u8;4] = [0;4];
if value.is_ascii_alphabetic() || value == '_' {
let value = value.to_ascii_uppercase();
value.encode_utf8(&mut buf);
let byte : u8 = buf[0];
assert_eq!(value, byte as char );
Ok(InternalLetter(byte))
} else {
Err(HangmanError::InvalidLetter)
}
}
}
impl From<InternalLetter> for char {
fn from(l: InternalLetter) -> Self {
l.0 as char
}
}
struct Game {
guesses_left: u8,
current_guesses: Vec<GameLetter>,
state: GameState,
word: &'static [char]
#[wasm_bindgen(inspectable)]
pub struct WordLetter {
#[wasm_bindgen(readonly)]
pub letter: InternalLetter,
#[wasm_bindgen(readonly)]
pub state: LetterGuessedState,
#[wasm_bindgen]
#[derive(Clone)]
pub struct JSGame {
pub guesses_left: Option<u8>,
pub current_guesses: JSGuessed,
pub state: GameState,
pub word: &'static str
#[wasm_bindgen(inspectable)]
pub struct GuessLetter {
#[wasm_bindgen(readonly)]
pub letter: InternalLetter,
#[wasm_bindgen(readonly)]
pub state: GuessResult,
impl<T> From<T> for JSGuessed where T: Iterator<Item = GameLetter> {
fn from(letters: T) -> Self {
let chars : Box<String> = Box::new(letters.map(|l| match l {
GameLetter::CorrectGuess(c) => c,
GameLetter::Unguessed => '_',
}).collect());
fn to_in_progress(letters: &Vec<WordLetter>) -> String {
let mapper = |l: &WordLetter| match l {
WordLetter {letter, state: LetterGuessedState::CorrectGuess } => (u8::from(*letter)),
WordLetter {state: LetterGuessedState::Unguessed, .. } => b"_"[0],
};
string_repr(letters, mapper)
}
Self(chars)
fn to_game_end(letters: &Vec<WordLetter>) -> String {
let mapper = |l: &WordLetter| l.letter.into();
string_repr(letters, mapper)
}
#[wasm_bindgen(getter)]
pub fn correct_guesses(&self) -> Box<[JsValue]> {
let correct: Vec<JsValue> = self
.guesses
.iter()
.filter_map(|g| {
if let GuessLetter{ letter, state: GuessResult::Correct } = g {
let s : String = (*letter).into();
let j : JsValue = (*s).into();
Some(j)
} else {
None
}
})
.collect();
correct.into_boxed_slice()
#[wasm_bindgen(getter, skip_typescript)]
pub fn incorrect_guesses(&self) -> Box<[JsValue]> {
let incorrect: Vec<JsValue> = self
.guesses
.iter()
.filter_map(|g| {
if let GuessLetter{ letter, state: GuessResult::Incorrect } = g {
let s : String = (*letter).into();
let j : JsValue = (*s).into();
Some(j)
} else {
None
}
})
.collect();
incorrect.into_boxed_slice()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn internal_letter_works_for_letters_and_underscore() -> Result<(), HangmanError> {
let lower : char = 'a';
let upper : char = 'Z';
let under : char = '_';
let lower_r : InternalLetter = lower.try_into()?;
assert_eq!("A".to_string(), String::from(lower_r));
let upper_r : InternalLetter = upper.try_into()?;
assert_eq!("Z".to_string(), String::from(upper_r));
let under_r : InternalLetter = under.try_into()?;
assert_eq!("_".to_string(), String::from(under_r));
Ok(())
}
#[test]
fn internal_letter_does_not_work_for_digits() {
let digit = '9';
assert!(InternalLetter::try_from(digit).is_err());
}
}