diff --git a/rust/cli_player/src/main.rs b/rust/cli_player/src/main.rs index c11497f..3b48772 100644 --- a/rust/cli_player/src/main.rs +++ b/rust/cli_player/src/main.rs @@ -18,7 +18,8 @@ use sea_battle_backend::data::GameRules; async fn run_app(terminal: &mut Terminal) -> Result<(), Box> { // Temporary code - let res = configure_game_rules::configure_play_rules(GameRules::default(), terminal)?; // select_bot_type::select_bot_type(terminal)?; + // let res = configure_game_rules::configure_play_rules(GameRules::default(), terminal)?; // select_bot_type::select_bot_type(terminal)?; + let res = set_boats_layout::set_boat_layout(&GameRules::default(), terminal)?; // select_bot_type::select_bot_type(terminal)?; Err(io::Error::new( ErrorKind::Other, format!("result: {:?}", res), diff --git a/rust/cli_player/src/ui_screens/mod.rs b/rust/cli_player/src/ui_screens/mod.rs index 5a93225..3f3df92 100644 --- a/rust/cli_player/src/ui_screens/mod.rs +++ b/rust/cli_player/src/ui_screens/mod.rs @@ -1,6 +1,7 @@ pub mod configure_game_rules; pub mod select_bot_type; pub mod select_play_mode; +pub mod set_boats_layout; pub mod utils; #[derive(Debug)] diff --git a/rust/cli_player/src/ui_screens/set_boats_layout.rs b/rust/cli_player/src/ui_screens/set_boats_layout.rs new file mode 100644 index 0000000..d386bae --- /dev/null +++ b/rust/cli_player/src/ui_screens/set_boats_layout.rs @@ -0,0 +1,75 @@ +use std::io; +use std::time::{Duration, Instant}; + +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::*; + +use crate::constants::*; +use crate::ui_screens::utils::centered_rect_size; +use crate::ui_screens::ScreenResult; +use crate::ui_widgets::game_map_widget::GameMapWidget; + +struct SetBotsLayoutScreen { + curr_boat: usize, + layout: BoatsLayout, +} + +pub fn set_boat_layout( + rules: &GameRules, + terminal: &mut Terminal, +) -> io::Result> { + let mut model = SetBotsLayoutScreen { + curr_boat: 0, + layout: BoatsLayout::gen_random_for_rules(rules) + .expect("Failed to generate initial boats layout"), + }; + + let mut last_tick = Instant::now(); + loop { + terminal.draw(|f| ui(f, &mut model, rules))?; + + let timeout = TICK_RATE + .checked_sub(last_tick.elapsed()) + .unwrap_or_else(|| Duration::from_secs(0)); + + if event::poll(timeout)? { + if let Event::Key(key) = event::read()? { + match key.code { + KeyCode::Char('q') => return Ok(ScreenResult::Canceled), + KeyCode::Enter => { + if model.layout.is_valid(rules) { + return Ok(ScreenResult::Ok(model.layout)); + } + } + KeyCode::Down => model.curr_boat -= 1, + KeyCode::Up => model.curr_boat += model.layout.number_of_boats() - 1, + _ => {} + } + + model.curr_boat %= model.layout.number_of_boats(); + } + } + if last_tick.elapsed() >= TICK_RATE { + last_tick = Instant::now(); + } + } +} + +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 game_map_widget = GameMapWidget::new(rules); + + f.render_widget(game_map_widget, area); +} diff --git a/rust/cli_player/src/ui_widgets/game_map_widget.rs b/rust/cli_player/src/ui_widgets/game_map_widget.rs new file mode 100644 index 0000000..21ee061 --- /dev/null +++ b/rust/cli_player/src/ui_widgets/game_map_widget.rs @@ -0,0 +1,34 @@ +use sea_battle_backend::data::{GameRules, PlayConfiguration}; +use tui::buffer::Buffer; +use tui::layout::Rect; +use tui::widgets::{BorderType, Widget}; + +pub struct GameMapWidget<'a> { + rules: &'a GameRules, +} + +impl<'a> GameMapWidget<'a> { + pub fn new(rules: &'a GameRules) -> Self { + Self { rules } + } +} + +impl<'a> Widget for GameMapWidget<'a> { + fn render(self, area: Rect, buf: &mut Buffer) { + let alphabet = PlayConfiguration::default().ordinate_alphabet; + + let symbols = BorderType::line_symbols(BorderType::Plain); + + // 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; + buf.get_mut(o_x, o_y).set_symbol(symbols.cross); + buf.get_mut(o_x + 1, o_y).set_symbol(symbols.horizontal); + buf.get_mut(o_x, o_y + 1).set_symbol(symbols.vertical); + buf.get_mut(o_x + 1, o_y + 1).set_char('.'); + } + } + } +} diff --git a/rust/cli_player/src/ui_widgets/mod.rs b/rust/cli_player/src/ui_widgets/mod.rs index 0bfdfdc..5f35227 100644 --- a/rust/cli_player/src/ui_widgets/mod.rs +++ b/rust/cli_player/src/ui_widgets/mod.rs @@ -1,3 +1,4 @@ pub mod button_widget; pub mod checkbox_widget; +pub mod game_map_widget; pub mod text_editor_widget; diff --git a/rust/sea_battle_backend/src/data/boats_layout.rs b/rust/sea_battle_backend/src/data/boats_layout.rs index 50c7261..150b1c7 100644 --- a/rust/sea_battle_backend/src/data/boats_layout.rs +++ b/rust/sea_battle_backend/src/data/boats_layout.rs @@ -172,7 +172,7 @@ impl BoatPosition { } #[derive(serde::Serialize, serde::Deserialize, Debug, Clone, Default)] -pub struct BoatsLayout(Vec); +pub struct BoatsLayout(pub Vec); impl BoatsLayout { /// Generate a new invalid (empty) boats layout @@ -331,6 +331,10 @@ impl BoatsLayout { errors } + pub fn is_valid(&self, rules: &GameRules) -> bool { + self.errors(rules).is_empty() + } + pub fn find_boat_at_position(&self, pos: Coordinates) -> Option<&BoatPosition> { self.0.iter().find(|f| f.all_coordinates().contains(&pos)) }