From 70906aa5de9a6d540fff502ef1038454edae764b Mon Sep 17 00:00:00 2001 From: Alexandre Bury Date: Wed, 29 Nov 2017 02:07:42 -0800 Subject: [PATCH] Add mines example --- Readme.md | 2 +- doc/examples/mines.png | Bin 0 -> 16587 bytes examples/mines/Readme.md | 1 + examples/mines/game.rs | 86 ++++++++++++ examples/mines/main.rs | 288 +++++++++++++++++++++++++++++++++++++++ src/backend/curses/n.rs | 7 +- src/event.rs | 10 +- src/views/button.rs | 15 +- src/xy.rs | 10 ++ 9 files changed, 413 insertions(+), 6 deletions(-) create mode 100644 doc/examples/mines.png create mode 100644 examples/mines/Readme.md create mode 100644 examples/mines/game.rs create mode 100644 examples/mines/main.rs diff --git a/Readme.md b/Readme.md index 17556a9..0791661 100644 --- a/Readme.md +++ b/Readme.md @@ -56,7 +56,7 @@ Check out the other [examples](https://github.com/gyscos/Cursive/tree/master/exa lorem.rs example menubar.rs example select.rs example -list_view.rs example +mines example theme.rs example diff --git a/doc/examples/mines.png b/doc/examples/mines.png new file mode 100644 index 0000000000000000000000000000000000000000..2328819cb7c02c6aa8acf8a7600fa09f7810e03c GIT binary patch literal 16587 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 literal 0 HcmV?d00001 diff --git a/examples/mines/Readme.md b/examples/mines/Readme.md new file mode 100644 index 0000000..c05977b --- /dev/null +++ b/examples/mines/Readme.md @@ -0,0 +1 @@ +This is a slightly larger example, showing an implementation of Minesweeper. diff --git a/examples/mines/game.rs b/examples/mines/game.rs new file mode 100644 index 0000000..072c012 --- /dev/null +++ b/examples/mines/game.rs @@ -0,0 +1,86 @@ +use cursive::vec::Vec2; +use rand::{thread_rng, Rng}; +use std::cmp::max; + +#[derive(Clone, Copy)] +pub struct Options { + pub size: Vec2, + pub mines: usize, +} + +#[derive(Clone, Copy)] +pub enum Cell { + Bomb, + Free(usize), +} + +pub struct Board { + pub size: Vec2, + pub cells: Vec, +} + +impl Board { + pub fn new(options: Options) -> Self { + let n_cells = options.size.x * options.size.y; + if options.mines > n_cells { + // Something is wrong here... + // Use different options instead. + return Board::new(Options { + size: options.size, + mines: n_cells, + }); + } + + let mut board = Board { + size: options.size, + cells: vec![Cell::Free(0); n_cells], + }; + + for _ in 0..options.mines { + // Find a free cell to put a bomb + let i = loop { + let i = thread_rng().gen_range(0, n_cells); + + if let Cell::Bomb = board.cells[i] { + continue; + } + + break i; + }; + + // We know we'll go through since that's how we picked i... + board.cells[i] = Cell::Bomb; + // Increase count on adjacent cells + + let pos = Vec2::new(i % options.size.x, i / options.size.x); + for p in board.neighbours(pos) { + if let Some(&mut Cell::Free(ref mut n)) = board.get_mut(p) { + *n += 1; + } + } + } + + board + } + + fn get_mut(&mut self, pos: Vec2) -> Option<&mut Cell> { + self.cell_id(pos).map(move |i| &mut self.cells[i]) + } + + pub fn cell_id(&self, pos: Vec2) -> Option { + if pos < self.size { + Some(pos.x + pos.y * self.size.x) + } else { + None + } + } + + pub fn neighbours(&self, pos: Vec2) -> Vec { + let pos_min = pos.saturating_sub((1, 1)); + let pos_max = (pos + (2, 2)).or_min(self.size); + (pos_min.x..pos_max.x) + .flat_map(|x| (pos_min.y..pos_max.y).map(move |y| Vec2::new(x, y))) + .filter(|&p| p != pos) + .collect() + } +} diff --git a/examples/mines/main.rs b/examples/mines/main.rs new file mode 100644 index 0000000..39a646f --- /dev/null +++ b/examples/mines/main.rs @@ -0,0 +1,288 @@ +extern crate cursive; +extern crate rand; + +mod game; + +use cursive::Cursive; +use cursive::Printer; +use cursive::direction::Direction; +use cursive::event::{Event, EventResult, MouseButton, MouseEvent}; +use cursive::theme::{BaseColor, Color, ColorStyle}; +use cursive::vec::Vec2; +use cursive::views::{Button, Dialog, LinearLayout, SelectView, Panel}; + +fn main() { + let mut siv = Cursive::new(); + + siv.add_layer( + Dialog::new() + .title("Minesweeper") + .padding((2, 2, 1, 1)) + .content( + LinearLayout::vertical() + .child(Button::new_raw(" New game ", show_options)) + .child(Button::new_raw(" Best scores ", |s| { + s.add_layer(Dialog::info("Not yet!").title("Scores")) + })) + .child(Button::new_raw(" Exit ", |s| s.quit())), + ), + ); + + siv.run(); +} + + +fn show_options(siv: &mut Cursive) { + siv.add_layer( + Dialog::new() + .title("Select difficulty") + .content( + SelectView::new() + .item( + "Easy: 8x8, 10 mines", + game::Options { + size: Vec2::new(8, 8), + mines: 10, + }, + ) + .item( + "Medium: 16x16, 40 mines", + game::Options { + size: Vec2::new(16, 16), + mines: 40, + }, + ) + .item( + "Difficult: 24x24, 99 mines", + game::Options { + size: Vec2::new(24, 24), + mines: 99, + }, + ) + .on_submit(|s, option| { + s.pop_layer(); + new_game(s, *option); + }), + ) + .button("Back", |s| s.pop_layer()), + ); +} + +#[derive(Clone, Copy, PartialEq)] +enum Cell { + Visible(usize), + Flag, + Unknown, +} + +struct BoardView { + // Actual board, unknown to the player. + board: game::Board, + + // Visible board + overlay: Vec, + + focused: Option, + missing_mines: usize, +} + +impl BoardView { + pub fn new(options: game::Options) -> Self { + let overlay = vec![Cell::Unknown; options.size.x * options.size.y]; + let board = game::Board::new(options); + BoardView { + board, + overlay, + focused: None, + missing_mines: options.mines, + } + } + + fn get_cell(&self, mouse_pos: Vec2, offset: Vec2) -> Option { + mouse_pos + .checked_sub(offset) + .map(|pos| pos.map_x(|x| x / 2)) + .and_then(|pos| { + if pos.fits_in(self.board.size) { + Some(pos) + } else { + None + } + }) + } + + fn flag(&mut self, pos: Vec2) { + if let Some(i) = self.board.cell_id(pos) { + let new_cell = match self.overlay[i] { + Cell::Unknown => Cell::Flag, + Cell::Flag => Cell::Unknown, + other => other, + }; + self.overlay[i] = new_cell; + } + } + + fn reveal(&mut self, pos: Vec2) -> EventResult { + if let Some(i) = self.board.cell_id(pos) { + if self.overlay[i] != Cell::Unknown { + return EventResult::Consumed(None); + } + + // Action! + match self.board.cells[i] { + game::Cell::Bomb => { + return EventResult::with_cb(|s| { + s.add_layer(Dialog::text("BOOOM").button("Ok", |s| { + s.pop_layer(); + s.pop_layer(); + })); + }) + } + game::Cell::Free(n) => { + self.overlay[i] = Cell::Visible(n); + if n == 0 { + // Reveal all surrounding cells + for p in self.board.neighbours(pos) { + self.reveal(p); + } + } + } + } + } + return EventResult::Consumed(None); + } + + fn auto_reveal(&mut self, pos: Vec2) -> EventResult { + if let Some(i) = self.board.cell_id(pos) { + if let Cell::Visible(n) = self.overlay[i] { + // First: is every possible cell tagged? + let neighbours = self.board.neighbours(pos); + let tagged = neighbours + .iter() + .filter_map(|&pos| self.board.cell_id(pos)) + .map(|i| self.overlay[i]) + .filter(|&cell| cell == Cell::Flag) + .count(); + if tagged != n { + return EventResult::Consumed(None); + } + + for p in neighbours { + let result = self.reveal(p); + if result.has_callback() { + return result; + } + } + } + } + + EventResult::Consumed(None) + } +} + +impl cursive::view::View for BoardView { + fn draw(&self, printer: &Printer) { + for (i, cell) in self.overlay.iter().enumerate() { + let x = (i % self.board.size.x) * 2; + let y = i / self.board.size.x; + + let text = match *cell { + Cell::Unknown => "[]", + Cell::Flag => "()", + Cell::Visible(n) => { + [" ", " 1", " 2", " 3", " 4", " 5", " 6", " 7", " 8"][n] + } + }; + + let color = match *cell { + Cell::Unknown => Color::RgbLowRes(3, 3, 3), + Cell::Flag => Color::RgbLowRes(4, 4, 2), + Cell::Visible(1) => Color::RgbLowRes(3, 5, 3), + Cell::Visible(2) => Color::RgbLowRes(5, 5, 3), + Cell::Visible(3) => Color::RgbLowRes(5, 4, 3), + Cell::Visible(4) => Color::RgbLowRes(5, 3, 3), + Cell::Visible(5) => Color::RgbLowRes(5, 2, 2), + Cell::Visible(6) => Color::RgbLowRes(5, 0, 1), + Cell::Visible(7) => Color::RgbLowRes(5, 0, 2), + Cell::Visible(8) => Color::RgbLowRes(5, 0, 3), + _ => Color::Dark(BaseColor::White), + }; + + printer.with_color( + ColorStyle::Custom { + back: color, + front: Color::Dark(BaseColor::Black), + }, + |printer| printer.print((x, y), text), + ); + } + } + + fn take_focus(&mut self, _: Direction) -> bool { + true + } + + fn on_event(&mut self, event: Event) -> EventResult { + match event { + Event::Mouse { + offset, + position, + event: MouseEvent::Press(btn), + } => { + // Get cell for position + if let Some(pos) = self.get_cell(position, offset) { + self.focused = Some(pos); + return EventResult::Consumed(None); + } + } + Event::Mouse { + offset, + position, + event: MouseEvent::Release(btn), + } => { + // Get cell for position + if let Some(pos) = self.get_cell(position, offset) { + if self.focused == Some(pos) { + // We got a click here! + match btn { + MouseButton::Left => return self.reveal(pos), + MouseButton::Right => { + self.flag(pos); + return EventResult::Consumed(None); + } + MouseButton::Middle => { + return self.auto_reveal(pos); + } + _ => (), + } + } + + self.focused = None; + } + } + _ => (), + } + + EventResult::Ignored + } + + fn required_size(&mut self, _: Vec2) -> Vec2 { + self.board.size.map_x(|x| 2 * x) + } +} + +fn new_game(siv: &mut Cursive, options: game::Options) { + let board = game::Board::new(options); + + siv.add_layer( + Dialog::new() + .title("Minesweeper") + .content( + LinearLayout::horizontal() + .child(Panel::new(BoardView::new(options))), + ) + .button("Quit game", |s| { + s.pop_layer(); + }), + ); +} diff --git a/src/backend/curses/n.rs b/src/backend/curses/n.rs index 0c73cd5..29ea54a 100644 --- a/src/backend/curses/n.rs +++ b/src/backend/curses/n.rs @@ -424,7 +424,12 @@ fn initialize_keymap() -> HashMap { // Then add some dynamic ones for c in 1..26 { - map.insert(c, Event::CtrlChar((b'a' - 1 + c as u8) as char)); + let event = match c { + 9 => Event::Key(Key::Tab), + 10 => Event::Key(Key::Enter), + other => Event::CtrlChar((b'a' - 1 + other as u8) as char), + }; + map.insert(c, event); } // Ncurses provides a F1 variable, but no modifiers diff --git a/src/event.rs b/src/event.rs index 4ac63b9..dc49d1c 100644 --- a/src/event.rs +++ b/src/event.rs @@ -76,7 +76,15 @@ impl EventResult { pub fn is_consumed(&self) -> bool { match *self { EventResult::Consumed(_) => true, - EventResult::Ignored => false, + _ => false, + } + } + + /// Returns `true` if `self` contains a callback. + pub fn has_callback(&self) -> bool { + match *self { + EventResult::Consumed(Some(_)) => true, + _ => false, } } diff --git a/src/views/button.rs b/src/views/button.rs index 50ed911..3fac701 100644 --- a/src/views/button.rs +++ b/src/views/button.rs @@ -28,6 +28,15 @@ pub struct Button { impl Button { /// Creates a new button with the given content and callback. pub fn new>(label: S, cb: F) -> Self + where + F: Fn(&mut Cursive) + 'static, + { + let label = label.into(); + Self::new_raw(format!("<{}>", label), cb) + } + + /// Creates a new button without angle brackets. + pub fn new_raw>(label: S, cb: F) -> Self where F: Fn(&mut Cursive) + 'static, { @@ -78,7 +87,7 @@ impl Button { } fn req_size(&self) -> Vec2 { - Vec2::new(2 + self.label.width(), 1) + Vec2::new(self.label.width(), 1) } } @@ -97,10 +106,10 @@ impl View for Button { }; let offset = - HAlign::Center.get_offset(self.label.len() + 2, printer.size.x); + HAlign::Center.get_offset(self.label.len(), printer.size.x); printer.with_color(style, |printer| { - printer.print((offset, 0), &format!("<{}>", self.label)); + printer.print((offset, 0), &self.label); }); } diff --git a/src/xy.rs b/src/xy.rs index 57955f4..35e2f20 100644 --- a/src/xy.rs +++ b/src/xy.rs @@ -21,6 +21,16 @@ impl XY { XY::new(f(self.x), f(self.y)) } + /// Creates a new `XY` by applying `f` to `x`, and carrying `y` over. + pub fn map_x T>(self, f: F) -> Self { + XY::new(f(self.x), self.y) + } + + /// Creates a new `XY` by applying `f` to `y`, and carrying `x` over. + pub fn map_y T>(self, f: F) -> Self { + XY::new(self.x, f(self.y)) + } + /// Destructure self into a pair. pub fn pair(self) -> (T, T) { (self.x, self.y)