1use eight_puzzle_core::hint::Hinter;
2use eight_puzzle_core::state::*;
3use find_folder::Search;
4use log::debug;
5use math::Scalar;
6use piston_window::*;
7use std::time::Instant;
8
9type Board = HashState;
10
11#[derive(Debug, Clone, Copy)]
12struct InGame {
13 move_count: usize,
14 hint_count: usize,
15 initial: Board,
16 board: Board,
17 show_hint: bool,
18}
19
20impl InGame {
21 fn move_board<F: Fn(&Board) -> Option<Board>>(&self, move_fn: F) -> InGame {
22 if let Some(new_board) = move_fn(&self.board) {
23 InGame {
24 move_count: self.move_count + 1,
25 hint_count: self.hint_count,
26 board: new_board,
27 show_hint: false,
28 ..*self
29 }
30 } else {
31 *self
32 }
33 }
34}
35
36#[derive(Debug)]
37enum Status {
38 Menu,
39 InGame(InGame),
40 Finish(Finish),
41}
42
43#[derive(Debug, Clone, Copy)]
44struct Finish {
45 move_count: usize,
46 hint_count: usize,
47 board: Board,
48}
49impl Status {
50 fn start(hinter: &Hinter<Board>) -> Status {
51 let initial = *hinter.random_state().unwrap();
52 Status::InGame(InGame {
53 move_count: 0,
54 hint_count: 0,
55 initial,
56 board: initial,
57 show_hint: false,
58 })
59 }
60}
61
62struct Resource {
63 goal: Board,
64 hinter: Hinter<Board>,
65 window: PistonWindow,
66 glyphs: Glyphs,
67}
68
69impl Resource {
70 fn draw_white_text(
71 glyphs: &mut Glyphs,
72 c: Context,
73 g: &mut G2d,
74 x: math::Scalar,
75 y: math::Scalar,
76 size: types::FontSize,
77 text: &str,
78 ) {
79 text::Text::new_color([1.0, 1.0, 1.0, 1.0], size)
80 .draw(text, glyphs, &c.draw_state, c.transform.trans(x, y), g)
81 .unwrap_or_else(|e| panic!("Failed to write text: {}", e))
82 }
83}
84
85pub struct Game {
86 resource: Resource,
87 status: Status,
88}
89
90impl Game {
91 pub fn new() -> Game {
92 let mut window: PistonWindow = WindowSettings::new("Eight Puzzle", (640, 580))
93 .build()
94 .unwrap_or_else(|e| panic!("Failed to build PistonWindow: {}", e));
95
96 let mut exe_folder = std::env::current_exe()
97 .unwrap_or_else(|e| panic!("Failed to locate current file path: {}", e));
98 exe_folder.pop();
99 let assets = Search::KidsThenParents(1, 2)
100 .of(exe_folder)
101 .for_folder("Resources")
102 .unwrap_or_else(|e| panic!("Resources not found: {}", e));
103
104 let glyphs = window
105 .load_font(assets.join("FiraSans-Regular.ttf"))
106 .unwrap_or_else(|e| panic!("Failed to create glyphs: {}", e));
107
108 let goal = Board::from_array(&[1, 2, 3, 4, 0, 5, 6, 7, 8]);
109 let hinter: Hinter<Board> = Hinter::new(goal);
110
111 window.set_lazy(true);
112 Game {
113 resource: Resource {
114 goal,
115 hinter,
116 window,
117 glyphs,
118 },
119 status: Status::Menu,
120 }
121 }
122
123 pub fn next(&mut self) -> Option<()> {
124 let Game { resource, status } = self;
125 debug!("{:?} status: {:?}.", Instant::now(), status);
126 resource
127 .window
128 .next()
129 .and_then(|e| {
130 debug!("{:?} event: {:?}.", Instant::now(), e);
131 match status {
132 Status::Menu => Game::on_menu(resource, &e),
133 Status::InGame(in_game) => Game::in_game(resource, in_game, &e),
134 Status::Finish(finish) => Game::on_finish(resource, &e, finish),
135 }
136 })
137 .map(|new_status| *status = new_status)
138 }
139
140 fn on_finish(resource: &mut Resource, e: &Event, finish: &Finish) -> Option<Status> {
141 let Resource {
142 window,
143 glyphs,
144 hinter,
145 ..
146 } = resource;
147 let Finish {
148 move_count,
149 hint_count,
150 board,
151 } = finish;
152
153 window.draw_2d(e, |c, g, device| {
154 clear([0.0, 0.0, 0.0, 0.0], g);
155 Resource::draw_white_text(
156 glyphs,
157 c,
158 g,
159 0.0,
160 25.0,
161 25,
162 &format!("Move: {}", move_count),
163 );
164 Resource::draw_white_text(
165 glyphs,
166 c,
167 g,
168 0.0,
169 50.0,
170 25,
171 &format!("Hint: {}", hint_count),
172 );
173 Game::draw_board(glyphs, c, g, board, 0.0, 350.0, 300.0, 100);
174 Resource::draw_white_text(glyphs, c, g, 0.0, 500.0, 150, "Success");
175 Resource::draw_white_text(glyphs, c, g, 300.0, 25.0, 25, "Restart: R");
176 Resource::draw_white_text(glyphs, c, g, 300.0, 50.0, 25, "Quit: ESC");
177 glyphs.factory.encoder.flush(device);
178 });
179 match e.press_args() {
180 Some(Button::Keyboard(Key::R)) => Some(Status::start(hinter)),
181 Some(Button::Keyboard(Key::Escape)) => Some(Status::Menu),
182 _ => Some(Status::Finish(*finish)),
183 }
184 }
185
186 fn on_menu(resource: &mut Resource, e: &Event) -> Option<Status> {
187 let Resource {
188 window,
189 glyphs,
190 hinter,
191 ..
192 } = resource;
193
194 window.draw_2d(e, |c, g, device| {
195 clear([0.0, 0.0, 0.0, 0.0], g);
196 Resource::draw_white_text(glyphs, c, g, 60.0, 100.0, 100, "Eight Puzzle");
197 Resource::draw_white_text(glyphs, c, g, 140.0, 200.0, 50, "Press S to start");
198 glyphs.factory.encoder.flush(device);
199 });
200
201 match e.press_args() {
202 Some(Button::Keyboard(Key::S)) => Some(Status::start(hinter)),
203 Some(Button::Keyboard(Key::Escape)) => None,
204 _ => Some(Status::Menu),
205 }
206 }
207
208 fn draw_board(
209 glyphs: &mut Glyphs,
210 c: Context,
211 g: &mut G2d,
212 board: &Board,
213 x: math::Scalar,
214 mut y: math::Scalar,
215 draw_size: math::Scalar,
216 font_size: types::FontSize,
217 ) {
218 let arr = board.to_array();
219 let square_size = draw_size / 3.0;
220 y -= square_size * 2.0;
221 for i in 0..9 {
222 if arr[i] != 0 {
223 Resource::draw_white_text(
224 glyphs,
225 c,
226 g,
227 x + ((i % 3) as Scalar * square_size),
228 y + ((i / 3) as Scalar * square_size),
229 font_size,
230 &arr[i].to_string(),
231 );
232 }
233 }
234 }
235
236 fn in_game(resource: &mut Resource, in_game: &InGame, e: &Event) -> Option<Status> {
237 let Resource {
238 goal,
239 window,
240 glyphs,
241 hinter,
242 ..
243 } = resource;
244
245 if in_game.board == *goal {
246 return Some(Status::Finish(Finish {
247 move_count: in_game.move_count,
248 hint_count: in_game.hint_count,
249 board: in_game.board,
250 }));
251 }
252
253 window.draw_2d(e, |c, g, device| {
254 clear([0.0, 0.0, 0.0, 0.0], g);
255 Resource::draw_white_text(
256 glyphs,
257 c,
258 g,
259 0.0,
260 25.0,
261 25,
262 &format!("Move: {}", in_game.move_count),
263 );
264 Resource::draw_white_text(
265 glyphs,
266 c,
267 g,
268 0.0,
269 50.0,
270 25,
271 &format!("Hint: {}", in_game.hint_count),
272 );
273 Game::draw_board(glyphs, c, g, &in_game.board, 0.0, 350.0, 300.0, 100);
274 if in_game.show_hint {
275 Resource::draw_white_text(
276 glyphs,
277 c,
278 g,
279 0.0,
280 375.0,
281 25,
282 &format!("Hint: {}", hinter.hint(&in_game.board).unwrap()),
283 )
284 }
285 Resource::draw_white_text(glyphs, c, g, 300.0, 25.0, 25, "Move: Direction keys.");
286 Resource::draw_white_text(glyphs, c, g, 300.0, 50.0, 25, "Goal: ");
287 Game::draw_board(glyphs, c, g, &goal, 425.0, 225.0, 200.0, 50);
288 Resource::draw_white_text(glyphs, c, g, 300.0, 250.0, 25, "Hint: H");
289 Resource::draw_white_text(glyphs, c, g, 300.0, 275.0, 25, "Reset: R");
290 Resource::draw_white_text(glyphs, c, g, 300.0, 300.0, 25, "Quit: ESC");
291 glyphs.factory.encoder.flush(device);
292 });
293
294 match e.press_args() {
295 Some(Button::Keyboard(Key::Up)) => Some(Status::InGame(in_game.move_board(|b| b.up()))),
296 Some(Button::Keyboard(Key::Down)) => {
297 Some(Status::InGame(in_game.move_board(|b| b.down())))
298 }
299 Some(Button::Keyboard(Key::Left)) => {
300 Some(Status::InGame(in_game.move_board(|b| b.left())))
301 }
302 Some(Button::Keyboard(Key::Right)) => {
303 Some(Status::InGame(in_game.move_board(|b| b.right())))
304 }
305 Some(Button::Keyboard(Key::Escape)) => Some(Status::Menu),
306 Some(Button::Keyboard(Key::R)) => Some(Status::InGame(InGame {
307 move_count: 0,
308 hint_count: 0,
309 initial: in_game.initial,
310 board: in_game.initial,
311 show_hint: false,
312 })),
313 Some(Button::Keyboard(Key::H)) => Some(Status::InGame(InGame {
314 hint_count: in_game.hint_count + 1,
315 show_hint: true,
316 ..*in_game
317 })),
318 _ => Some(Status::InGame(*in_game)),
319 }
320 }
321}