Skip to main content

eight_puzzle/
lib.rs

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}