minesweeper 1.3.0

Simple minesweeper in Rust
use field::{Field, Content};
use ui::UI;
use ui::EndMessage;
use piston_window::*;
use common::{ParamType, MoveDestination, GameEndState};
use chrono::{DateTime, UTC, Duration};

enum RenderState {
    // redraw picture on current buffer
    First,

    // redraw picture on next buffer
    Second,

    // needn't redraw picture
    None,
}

impl RenderState {
    // Update render state and return flag necessity of redrawing pickture
    fn update_state(&mut self) -> bool {
        match *self {
            RenderState::First => *self = RenderState::Second,
            RenderState::Second => *self = RenderState::None,
            RenderState::None => return false,
        }

        true
    }

    fn reset(&mut self) {
        *self = RenderState::First
    }
}

pub struct Game<'a> {
    field: Field,
    ui: UI<'a>,
    msg: EndMessage<'a>,
    glyphs: Glyphs,
    mouse_x: f64,
    mouse_y: f64,
    game_ended: bool,
    panel_width: u32,
    in_ui: bool,
    game_start: Option<DateTime<UTC>>,
    last_time: Option<DateTime<UTC>>,
    game_end: Option<DateTime<UTC>>,
    // state of rendering for drawing picture on both buffers
    render_state: RenderState,
}

impl<'a> Game<'a> {
    pub fn new(glyphs: Glyphs, width: u32, height: u32, mines: u32) -> Game<'a> {
        Game {
            field: Field::new(width, height, mines),
            ui: UI::new(width, height, mines),
            msg: EndMessage::new(),
            glyphs: glyphs,
            mouse_x: 0.0,
            mouse_y: 0.0,
            game_ended: false,
            panel_width: 350,
            in_ui: false,
            game_start: None,
            game_end: None,
            last_time: None,
            render_state: RenderState::First,
        }
    }

    pub fn render(&mut self, window: &mut PistonWindow, e: &Input) {
        if !self.render_state.update_state() {
            return;
        }

        let field_rect = self.get_field_rect(window);
        let ui_rect = self.get_ui_rect(window);
        let msg_rect = self.get_msg_rect(window);
        window.draw_2d(e, |c, g| {
            clear([0.0, 0.0, 0.0, 1.0], g);
            self.field.draw(c, g, field_rect, &mut self.glyphs);
            let dur = match self.game_start {
                Some(game_start) => {
                    match self.game_end {
                        Some(game_end) => game_end - game_start,
                        None => UTC::now() - game_start,
                    }
                }
                None => Duration::seconds(0), 
            };

            self.ui.draw(c,
                         g,
                         ui_rect,
                         &mut self.glyphs,
                         self.field.total_mines(),
                         self.field.count_marked(),
                         dur);
            self.msg.draw(c, g, msg_rect, &mut self.glyphs);
        });
    }

    fn get_field_rect(&self, window: &PistonWindow) -> [u32; 4] {
        let mut w = window.size().width - self.panel_width;
        w = (w / self.field.get_width()) * self.field.get_width();
        let mut h = window.size().height;
        h = (h / self.field.get_height()) * self.field.get_height();
        [0, 0, w, h]
    }

    fn get_ui_rect(&self, window: &PistonWindow) -> [u32; 4] {
        let mut field_w = window.size().width - self.panel_width;
        field_w = (field_w / self.field.get_width()) * self.field.get_width();
        let w = window.size().width - field_w;
        let h = window.size().height;
        [field_w, 0, w, h]
    }

    fn get_msg_rect(&self, window: &PistonWindow) -> [u32; 4] {
        let w_w = window.size().width;
        let w_h = window.size().height;
        let (m_w, m_h) = EndMessage::size();

        [((w_w - m_w) / 2), ((w_h - m_h) / 2), m_w, m_h]
    }

    pub fn proc_key(&mut self, button: Button, window: &PistonWindow) {
        let mut need_redraw = true;
        if self.in_ui {
            match button {
                Button::Keyboard(key) => {
                    match key {
                        Key::H => {
                            match self.ui.proc_key(ParamType::Height) {
                                Some(h) => {
                                    self.in_ui = false;
                                    self.field.reinit_field(h, ParamType::Height);
                                    self.restart();
                                }
                                _ => need_redraw = false,
                            }
                        }
                        Key::M => {
                            match self.ui.proc_key(ParamType::Mines) {
                                Some(m) => {
                                    self.in_ui = false;
                                    self.field.reinit_field(m, ParamType::Mines);
                                    self.restart();
                                }
                                _ => need_redraw = false,
                            }
                        }
                        Key::W => {
                            match self.ui.proc_key(ParamType::Width) {
                                Some(w) => {
                                    self.in_ui = false;
                                    self.field.reinit_field(w, ParamType::Width);
                                    self.restart();
                                }
                                _ => need_redraw = false,
                            }
                        }
                        Key::Up => self.ui.change_selected(MoveDestination::Up),
                        Key::Down => self.ui.change_selected(MoveDestination::Down),
                        Key::Left => self.ui.change_selected(MoveDestination::Left),
                        Key::Right => self.ui.change_selected(MoveDestination::Right),
                        _ => need_redraw = false,
                    }
                }
                _ => need_redraw = false,
            }
        } else {
            match button {
                Button::Keyboard(key) => {
                    match key {
                        Key::R => self.restart(),
                        Key::Up => self.field.move_selection(MoveDestination::Up),
                        Key::Down => self.field.move_selection(MoveDestination::Down),
                        Key::Left => self.field.move_selection(MoveDestination::Left),
                        Key::Right => self.field.move_selection(MoveDestination::Right),
                        Key::Space => {
                            let ind = self.field.get_selected_ind();
                            self.open_cell(ind);
                        }
                        Key::LCtrl | Key::RCtrl => {
                            let ind = self.field.get_selected_ind();
                            self.toggle_mark(ind);
                        }
                        Key::H => {
                            self.ui.proc_key(ParamType::Height);
                            self.in_ui = true;
                        }
                        Key::M => {
                            self.ui.proc_key(ParamType::Mines);
                            self.in_ui = true;
                        }
                        Key::W => {
                            self.ui.proc_key(ParamType::Width);
                            self.in_ui = true;
                        }
                        _ => need_redraw = false,
                    }
                }
                Button::Mouse(btn) => {
                    let field_rect = self.get_field_rect(window);
                    let cell_w = field_rect[2] / self.field.get_width();
                    let cell_h = field_rect[3] / self.field.get_height();
                    let mouse_x = self.mouse_x.floor() as u32;
                    let mouse_y = self.mouse_y.floor() as u32;
                    if (mouse_x < field_rect[0]) || (mouse_x > field_rect[0] + field_rect[2]) ||
                       (mouse_y < field_rect[1]) ||
                       (mouse_y > field_rect[1] + field_rect[3]) {
                        return;
                    }
                    let x = (mouse_x - field_rect[0]) / cell_w;
                    let y = (mouse_y - field_rect[1]) / cell_h;
                    let w = self.field.get_width();
                    match btn {
                        MouseButton::Left => {
                            self.open_cell(x + y * w);
                        }
                        MouseButton::Right => {

                            self.toggle_mark(x + y * w);
                        }
                        _ => need_redraw = false,
                    }
                }
                Button::Controller(_) => {
                    // Doen't work for me (Logitech Rumble F510)
                    // println!("{:?}", btn);
                }

            }
        }

        if need_redraw {
            self.render_state.reset();
        }
    }

    fn toggle_mark(&mut self, i: u32) {
        if self.game_ended || self.field.revealed(i) {
            return;
        }
        self.field.toggle_mark(i);
    }

    fn open_cell(&mut self, i: u32) {
        if self.game_ended {
            return;
        }
        if self.game_start == None {
            self.game_start = Some(UTC::now());
        }

        self.field.reset_if_need(i);

        if !self.field.revealed(i) {
            self.check_reveal(i);
        } else {
            self.reveal_neighborhood(i);
        }
    }

    fn reveal_neighborhood(&mut self, i: u32) {
        let cells_count_opt = {
            if let &Content::Number(cells_count) = self.field.get_content(i) {
                Some(cells_count)
            } else {
                None
            }
        };

        if let Some(cells_count) = cells_count_opt {
            if self.field.get_neighborhood_count(i) == cells_count {
                let on_left_edge = i % self.field.get_width() == 0;
                let on_right_edge = (i + 1) % self.field.get_width() == 0;

                for rdelta in -1..2i32 {
                    for cdelta in -1..2i32 {
                        if on_left_edge && (cdelta == -1) {
                            continue;
                        }
                        if on_right_edge && (cdelta == 1) {
                            continue;
                        }
                        let tgt = (i as i32) + rdelta * (self.field.get_width() as i32) + cdelta;
                        if (tgt < 0) || (tgt >= self.field.get_size() as i32) {
                            continue;
                        }
                        self.check_reveal(tgt as u32);
                    }
                }
            }
        }
    }

    pub fn check_reveal(&mut self, i: u32) {
        if self.field.marked(i) {
            return;
        }
        match *self.field.reveal(i) {
            Content::Mine(_) => {
                self.field.reveal_all();
                self.game_ended = true;
                self.field.set_killer(i);
                self.game_end = Some(UTC::now());
                self.msg.show(GameEndState::Lose);
                println!("Game over :(");
            }
            Content::None => {
                self.field.chain_reveal(i);
                if self.field.is_victory() {
                    self.msg.show(GameEndState::Win);
                    println!("You win :)");
                    self.game_ended = true;
                    self.game_end = Some(UTC::now());
                }
            }
            Content::Number(_i) => {
                if self.field.is_victory() {
                    self.msg.show(GameEndState::Win);
                    println!("You win :)");
                    self.game_ended = true;
                    self.game_end = Some(UTC::now());
                }
            }
        }
    }

    pub fn mouse_move(&mut self, mouse_rel: [f64; 2]) {
        self.mouse_x = mouse_rel[0];
        self.mouse_y = mouse_rel[1];
    }

    pub fn update_state(&mut self) {
        if let Some(_) = self.game_start {
            if let None = self.game_end {
                let now = UTC::now();
                match self.last_time {
                    Some(last_time) => {
                        let time_from_last_tick = now - last_time;
                        if time_from_last_tick >= Duration::seconds(1) {
                            self.last_time = Some(now);
                            self.render_state.reset();
                        }
                    }
                    None => self.last_time = Some(now),
                }
            }
        }
    }

    pub fn resize(&mut self) {
        self.render_state.reset();
    }

    fn restart(&mut self) {
        self.game_ended = false;
        self.msg.hide();
        self.game_start = None;
        self.game_end = None;
        self.last_time = None;
        self.field.restart();
        self.render_state.reset();
    }
}