From 8728cbb6122d18ae70a89bf7f08dba09e5f7662d Mon Sep 17 00:00:00 2001 From: Pierre Hubert Date: Fri, 7 Oct 2022 11:34:31 +0200 Subject: [PATCH] Can show legend on game map --- .../src/ui_screens/configure_game_rules.rs | 4 +- .../src/ui_screens/select_bot_type.rs | 2 +- .../src/ui_screens/select_play_mode.rs | 2 +- .../src/ui_screens/set_boats_layout.rs | 36 +++++--- rust/cli_player/src/ui_screens/utils.rs | 2 +- .../src/ui_widgets/button_widget.rs | 2 +- .../src/ui_widgets/game_map_widget.rs | 82 +++++++++++++++++-- 7 files changed, 108 insertions(+), 22 deletions(-) diff --git a/rust/cli_player/src/ui_screens/configure_game_rules.rs b/rust/cli_player/src/ui_screens/configure_game_rules.rs index 5559f75..94bb344 100644 --- a/rust/cli_player/src/ui_screens/configure_game_rules.rs +++ b/rust/cli_player/src/ui_screens/configure_game_rules.rs @@ -140,7 +140,7 @@ pub fn configure_play_rules( } fn ui(f: &mut Frame, model: &mut GameRulesConfigurationScreen) { - let area = centered_rect_size(50, 16, f.size()); + let area = centered_rect_size(50, 16, &f.size()); let block = Block::default().title("Game rules").borders(Borders::ALL); f.render_widget(block, area); @@ -218,7 +218,7 @@ fn ui(f: &mut Frame, model: &mut GameRulesConfigurationScreen) { // Error message (if any) if let Some(msg) = model.rules.get_errors().first() { - let area = centered_rect_size(msg.len() as u16, 1, *chunks.last().unwrap()); + let area = centered_rect_size(msg.len() as u16, 1, chunks.last().unwrap()); let err = Paragraph::new(*msg).style(Style::default().fg(Color::Red)); f.render_widget(err, area); } diff --git a/rust/cli_player/src/ui_screens/select_bot_type.rs b/rust/cli_player/src/ui_screens/select_bot_type.rs index 9bbf224..80dcdb9 100644 --- a/rust/cli_player/src/ui_screens/select_bot_type.rs +++ b/rust/cli_player/src/ui_screens/select_bot_type.rs @@ -62,7 +62,7 @@ pub fn select_bot_type( } fn ui(f: &mut Frame, model: &mut SelectPlayModeScreen) { - let area = centered_rect_size(60, model.types.len() as u16 * 2 + 2, f.size()); + let area = centered_rect_size(60, model.types.len() as u16 * 2 + 2, &f.size()); // Create a List from all list items and highlight the currently selected one let items = model diff --git a/rust/cli_player/src/ui_screens/select_play_mode.rs b/rust/cli_player/src/ui_screens/select_play_mode.rs index 852e81b..b9beb7e 100644 --- a/rust/cli_player/src/ui_screens/select_play_mode.rs +++ b/rust/cli_player/src/ui_screens/select_play_mode.rs @@ -80,7 +80,7 @@ pub fn select_play_mode( } fn ui(f: &mut Frame, model: &mut SelectPlayModeScreen) { - let area = centered_rect_size(50, 5, f.size()); + let area = centered_rect_size(50, 5, &f.size()); // Create a List from all list items and highlight the currently selected one let items = AVAILABLE_PLAY_MODES diff --git a/rust/cli_player/src/ui_screens/set_boats_layout.rs b/rust/cli_player/src/ui_screens/set_boats_layout.rs index d386bae..d8d91d1 100644 --- a/rust/cli_player/src/ui_screens/set_boats_layout.rs +++ b/rust/cli_player/src/ui_screens/set_boats_layout.rs @@ -5,8 +5,6 @@ use crossterm::event; use crossterm::event::{Event, KeyCode}; use tui::backend::Backend; use tui::style::*; -use tui::text::*; -use tui::widgets::*; use tui::{Frame, Terminal}; use sea_battle_backend::data::*; @@ -14,7 +12,7 @@ use sea_battle_backend::data::*; use crate::constants::*; use crate::ui_screens::utils::centered_rect_size; use crate::ui_screens::ScreenResult; -use crate::ui_widgets::game_map_widget::GameMapWidget; +use crate::ui_widgets::game_map_widget::{ColoredCells, GameMapWidget}; struct SetBotsLayoutScreen { curr_boat: usize, @@ -48,8 +46,7 @@ pub fn set_boat_layout( return Ok(ScreenResult::Ok(model.layout)); } } - KeyCode::Down => model.curr_boat -= 1, - KeyCode::Up => model.curr_boat += model.layout.number_of_boats() - 1, + KeyCode::Char('n') => model.curr_boat += model.layout.number_of_boats() - 1, _ => {} } @@ -63,13 +60,30 @@ pub fn set_boat_layout( } fn ui(f: &mut Frame, model: &mut SetBotsLayoutScreen, rules: &GameRules) { - let area = centered_rect_size( - rules.map_width as u16 + 10, - rules.map_height as u16 + 10, - f.size(), - ); + let current_boat = ColoredCells { + color: Color::Green, + cells: model.layout.0[model.curr_boat].all_coordinates(), + }; - let game_map_widget = GameMapWidget::new(rules); + let mut other_boats_cells = vec![]; + for boat in &model.layout.0 { + for pos in boat.all_coordinates() { + other_boats_cells.push(pos); + } + } + let other_boats = ColoredCells { + color: Color::DarkGray, + cells: other_boats_cells, + }; + + let game_map_widget = GameMapWidget::new(rules) + .set_default_empty_char(' ') + .add_colored_cells(current_boat) + .add_colored_cells(other_boats) + .set_legend("n next boat \nr rotate boat \n\nā†“ ā†‘ ā†’ ā† move boat \n\nEnter confirm layout"); + + let (w, h) = game_map_widget.estimated_size(); + let area = centered_rect_size(w, h, &f.size()); f.render_widget(game_map_widget, area); } diff --git a/rust/cli_player/src/ui_screens/utils.rs b/rust/cli_player/src/ui_screens/utils.rs index 94ab278..69633a5 100644 --- a/rust/cli_player/src/ui_screens/utils.rs +++ b/rust/cli_player/src/ui_screens/utils.rs @@ -28,7 +28,7 @@ pub fn centered_rect_percentage(percent_x: u16, percent_y: u16, r: Rect) -> Rect } /// helper function to create a centered rect using up certain container size -pub fn centered_rect_size(width: u16, height: u16, parent: Rect) -> Rect { +pub fn centered_rect_size(width: u16, height: u16, parent: &Rect) -> Rect { if parent.width < width || parent.height < height { return Rect { x: parent.x, diff --git a/rust/cli_player/src/ui_widgets/button_widget.rs b/rust/cli_player/src/ui_widgets/button_widget.rs index 32a3c64..bc7b90a 100644 --- a/rust/cli_player/src/ui_widgets/button_widget.rs +++ b/rust/cli_player/src/ui_widgets/button_widget.rs @@ -33,7 +33,7 @@ impl Widget for ButtonWidget { fn render(self, area: Rect, buf: &mut Buffer) { let label = format!(" {} ", self.label); - let area = centered_rect_size(label.len() as u16, 1, area); + let area = centered_rect_size(label.len() as u16, 1, &area); let input = Paragraph::new(label.as_ref()).style(match (self.disabled, self.is_hovered) { (true, _) => Style::default(), diff --git a/rust/cli_player/src/ui_widgets/game_map_widget.rs b/rust/cli_player/src/ui_widgets/game_map_widget.rs index e638475..6bffbc2 100644 --- a/rust/cli_player/src/ui_widgets/game_map_widget.rs +++ b/rust/cli_player/src/ui_widgets/game_map_widget.rs @@ -1,15 +1,63 @@ -use sea_battle_backend::data::{GameRules, PlayConfiguration}; +use crate::ui_screens::utils::centered_rect_size; +use sea_battle_backend::data::{Coordinates, GameRules, PlayConfiguration}; +use std::fmt::Display; use tui::buffer::Buffer; use tui::layout::Rect; +use tui::style::{Color, Style}; use tui::widgets::{BorderType, Widget}; +pub struct ColoredCells { + pub color: Color, + pub cells: Vec, +} + pub struct GameMapWidget<'a> { rules: &'a GameRules, + default_empty_character: char, + colored_cells: Vec, + legend: Option, } impl<'a> GameMapWidget<'a> { pub fn new(rules: &'a GameRules) -> Self { - Self { rules } + Self { + rules, + default_empty_character: '.', + colored_cells: vec![], + legend: None, + } + } + + pub fn set_default_empty_char(mut self, c: char) -> Self { + self.default_empty_character = c; + self + } + + pub fn add_colored_cells(mut self, c: ColoredCells) -> Self { + self.colored_cells.push(c); + self + } + + pub fn set_legend(mut self, legend: D) -> Self { + self.legend = Some(legend.to_string()); + self + } + + pub fn grid_size(&self) -> (u16, u16) { + let w = self.rules.map_width as u16 * 2 + 1; + let h = self.rules.map_height as u16 * 2 + 1; + + (w, h) + } + + pub fn estimated_size(&self) -> (u16, u16) { + let (w, mut h) = self.grid_size(); + + if let Some(l) = &self.legend { + h += 1 + l.split('\n').count() as u16; + } + + (w, h) } } @@ -22,8 +70,9 @@ impl<'a> Widget for GameMapWidget<'a> { // Paint game grid for y in 0..(self.rules.map_height + 1) { for x in 0..(self.rules.map_width + 1) { - let o_x = x as u16 * 2; - let o_y = y as u16 * 2; + let o_x = area.x + (x as u16 * 2); + let o_y = area.y + (y as u16 * 2); + buf.get_mut(o_x, o_y).set_symbol(match (x, y) { (0, 0) => symbols.top_left, @@ -52,9 +101,32 @@ impl<'a> Widget for GameMapWidget<'a> { } if x < self.rules.map_width && y < self.rules.map_height { - buf.get_mut(o_x + 1, o_y + 1).set_char('.'); + let cell = buf + .get_mut(o_x + 1, o_y + 1) + .set_char(self.default_empty_character); + + if let Some(c) = self + .colored_cells + .iter() + .find(|c| c.cells.contains(&Coordinates::new(x as i32, y as i32))) + { + cell.set_bg(c.color); + } } } } + + // Paint legend (if any) + if let Some(legend) = &self.legend { + let (_, mut y) = self.grid_size(); + y += area.y + 1; + + for line in legend.split('\n') { + let center_rect = centered_rect_size(line.len() as u16, 1, &area); + let x = center_rect.x; + buf.set_string(x, y, line, Style::default()); + y += 1; + } + } } }