1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
use std::{fmt, ops::Deref};

use crate::{
    state::{GuessError, State},
    Matches, words::WordSet,
};
use eyre::{ensure, Result};

/// A Wrapper over [`State`] that manages
/// creation and playing of games
pub struct Game {
    state: State,
    hard_mode: bool,
    game_type: GameType,
}

impl Deref for Game {
    type Target = State;

    fn deref(&self) -> &Self::Target {
        &self.state
    }
}

impl Game {
    /// Create a new game based on the current date (official style)
    #[cfg(feature = "time")]
    pub fn new(word_set: WordSet<'static>) -> Result<Self> {
        use eyre::WrapErr;
        let now =
            time::OffsetDateTime::now_local().wrap_err("could not determine local timezone")?;
        Ok(Self::from_date(now.date(), word_set))
    }

    /// Create a new game based on the given word
    pub fn custom(solution: String, word_set: WordSet<'static>) -> Result<Self> {
        ensure!(
            word_set.solutions.contains(&&*solution),
            "{} is not a valid solution",
            solution
        );
        Ok(Self::new_raw(solution, GameType::Custom, word_set))
    }

    /// Create a new game based on the given date
    #[cfg(feature = "time")]
    pub fn from_date(date: time::Date, word_set: WordSet<'static>) -> Self {
        let day = word_set.get_day(date);
        Self::from_day(day, word_set)
    }

    /// Create a new game based on the given day number
    pub fn from_day(day: usize, word_set: WordSet<'static>) -> Self {
        let solution = word_set.get_solution(day).to_owned();
        Self::new_raw(solution, GameType::Daily(day), word_set)
    }

    fn new_raw(solution: String, game_type: GameType, word_set: WordSet<'static>) -> Self {
        Self {
            state: State::new(solution, word_set),
            hard_mode: false,
            game_type,
        }
    }

    /// Sets the play style of this game to 'hard mode'.
    /// This means that any exact matches found must be
    /// re-used in later guesses
    pub fn hard_mode(&mut self) {
        self.hard_mode = true;
    }

    /// Get the [`GameType`] for this game
    pub fn game_type(&self) -> GameType {
        self.game_type
    }

    /// Make a guess.
    ///
    /// # Errors
    /// If the guess is an invalid word, or if it doesn't match the
    /// requirements of hard mode, this function will return an error
    pub fn guess(&mut self, word: &str) -> Result<Matches, GuessError> {
        self.state.guess(word, self.hard_mode)
    }

    /// Display the share card for this game
    ///
    /// ```
    /// use cl_wordle::game::Game;
    /// let mut game = Game::from_day(0, cl_wordle::words::NYTIMES);
    /// game.guess("crane").unwrap();
    /// game.guess("carts").unwrap();
    /// game.guess("chair").unwrap();
    /// game.guess("cigar").unwrap();
    ///
    /// let share = game.share();
    /// let score_card = format!("{}", share);
    /// assert_eq!(score_card, r"Wordle 0 4/6
    /// 🟩🟨🟨⬛⬛
    /// 🟩🟨🟨⬛⬛
    /// 🟩⬛🟨🟨🟩
    /// 🟩🟩🟩🟩🟩");
    /// ```
    pub fn share(self) -> GameShare {
        GameShare(self)
    }
}

#[derive(Clone, Copy, Debug)]
pub enum GameType {
    Daily(usize),
    Custom,
}

impl fmt::Display for GameType {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            GameType::Daily(day) => write!(f, "{}", day),
            GameType::Custom => write!(f, "custom"),
        }
    }
}

/// Display the share card for this game
///
/// ```
/// use cl_wordle::game::Game;
/// let mut game = Game::from_day(0, cl_wordle::words::NYTIMES);
/// game.guess("crane").unwrap();
/// game.guess("carts").unwrap();
/// game.guess("chair").unwrap();
/// game.guess("cigar").unwrap();
///
/// let share = game.share();
/// let score_card = format!("{}", share);
/// assert_eq!(score_card, r"Wordle 0 4/6
/// 🟩🟨🟨⬛⬛
/// 🟩🟨🟨⬛⬛
/// 🟩⬛🟨🟨🟩
/// 🟩🟩🟩🟩🟩");
/// ```
pub struct GameShare(Game);

impl fmt::Display for GameShare {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "Wordle {game_type} ", game_type = self.0.game_type())?;
        self.0.display_score_card(f, self.0.hard_mode)?;
        Ok(())
    }
}