ascii_hangman_backend/
lib.rs1mod ascii_art;
4mod dictionary;
5pub mod game;
6mod image;
7mod secret;
8use crate::dictionary::ConfigParseError;
9use crate::dictionary::Dict;
10use crate::game::Game;
11use crate::game::State;
12use crate::image::Image;
13
14pub const VERSION: Option<&'static str> = option_env!("CARGO_PKG_VERSION");
15pub const AUTHOR: &str = "(c) Jens Getreu, 2016-2021.";
16
17pub const TITLE: &str = "ASCII-Hangman for Kids\n";
19
20pub const CHANGE_IMAGE_MAX: usize = 5;
26
27pub const LIVES: u8 = 7;
29pub const CONF_TEMPLATE: &str = "# Add own secrets here; one per line.\r
32\r
33secrets:\r
34- guess me\r
35- _good l_uck\r
36- \"_der Hund:_| the dog\"\r
37- _3*_7_=21_\r
38\r
39\r
40# Uncomment 3 lines to use an optional custom image:\r
41\r
42#image: |1\r
43# ::\r
44# C|__|\r
45";
46
47pub const CONF_TEMPLATE_SHORT: &str = "# Replace the sample secrets with your own; one per line.\r
49\r
50secrets:\r
51- guess me\r
52- _good l_uck\r
53- \"_der Hund:_| the dog\"\r
54- _3*_7_=21_\r
55";
56
57#[derive(Debug)]
59pub struct Backend {
60 dict: Dict,
61 game: Game,
62 image: Image,
63 change_image: Option<usize>,
64}
65
66pub trait HangmanBackend {
69 fn new(config: &str) -> Result<Self, ConfigParseError>
71 where
72 Self: std::marker::Sized;
73
74 fn process_user_input(&mut self, inp: &str);
76
77 fn render_image(&self) -> String;
79
80 fn get_image_dimension(&self) -> (u8, u8);
82
83 fn render_secret(&self) -> String;
85
86 fn render_game_lifes(&self) -> String;
88
89 fn render_game_last_guess(&self) -> String;
91
92 fn render_instructions(&self) -> String;
94
95 fn get_state(&self) -> State;
97}
98
99impl HangmanBackend for Backend {
100 fn new(config: &str) -> Result<Self, ConfigParseError> {
101 let mut dict = Dict::from(config)?;
102 let secret = dict.get_random_secret().unwrap();
104 let game = Game::new(&secret, LIVES, dict.is_empty());
105 let mut change_image = None;
107 let mut image = Image::from_yaml(config).or_else(|_| {
108 change_image = Some(0);
110 Image::new()
111 })?;
112 image.update(&game);
113 Ok(Self {
114 dict,
115 game,
116 image,
117 change_image,
118 })
119 }
120
121 fn process_user_input(&mut self, inp: &str) {
122 match self.game.state {
123 State::Victory => {
124 let secret = self.dict.get_random_secret().unwrap();
127 self.game = Game::new(&secret, LIVES, self.dict.is_empty());
128 if let Some(n) = self.change_image {
130 if n == CHANGE_IMAGE_MAX - 1 {
131 if let Ok(new_image) = Image::new() {
133 self.image = new_image;
134 };
135 self.change_image = Some(0);
136 } else {
137 self.change_image = Some(n + 1);
138 };
139 };
140 self.image.update(&self.game);
141 }
142
143 State::VictoryGameOver => {}
144
145 State::Defeat | State::DefeatGameOver => {
146 self.dict.add((self.game.secret).to_raw_string());
148 let secret = self.dict.get_random_secret().unwrap();
150 self.game = Game::new(&secret, LIVES, self.dict.is_empty());
151 self.image.update(&self.game);
152 }
153 State::Ongoing => {
154 self.game.guess(inp.chars().next().unwrap_or(' '));
155 self.image.update(&self.game);
157 }
158 }
159 }
160
161 fn render_image(&self) -> String {
162 format!("{}", self.image)
163 }
164
165 #[allow(dead_code)]
166 fn get_image_dimension(&self) -> (u8, u8) {
167 self.image.dimension
168 }
169
170 fn render_secret(&self) -> String {
171 format!("{}", self.game.secret)
172 }
173
174 fn render_game_lifes(&self) -> String {
175 format!("Lifes: {}", self.game.lifes)
176 }
177
178 fn render_game_last_guess(&self) -> String {
179 format!("Last guess: {}", self.game.last_guess)
180 }
181
182 fn render_instructions(&self) -> String {
183 match self.game.state {
184 State::Victory => String::from("Congratulations! You won!"),
185 State::VictoryGameOver => String::from("Congratulations! You won!"),
186 State::Defeat | State::DefeatGameOver => String::from("You lost."),
187 State::Ongoing => String::from("Type a letter, then press [Enter]:"),
188 }
189 }
190
191 fn get_state(&self) -> State {
192 self.game.state.clone()
193 }
194}