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());}}