Improve cli screens structures
This commit is contained in:
parent
276f4508c0
commit
d90560d330
@ -4,6 +4,11 @@ use clap::{Parser, ValueEnum};
|
|||||||
pub enum TestDevScreen {
|
pub enum TestDevScreen {
|
||||||
Popup,
|
Popup,
|
||||||
Input,
|
Input,
|
||||||
|
Confirm,
|
||||||
|
SelectBotType,
|
||||||
|
SelectPlayMode,
|
||||||
|
SetBoatsLayout,
|
||||||
|
ConfigureGameRules,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Parser, Debug)]
|
#[derive(Parser, Debug)]
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
use std::error::Error;
|
use std::error::Error;
|
||||||
use std::fmt::Debug;
|
|
||||||
use std::io;
|
use std::io;
|
||||||
use std::io::ErrorKind;
|
use std::io::ErrorKind;
|
||||||
|
|
||||||
@ -15,31 +14,50 @@ use tui::backend::{Backend, CrosstermBackend};
|
|||||||
use tui::Terminal;
|
use tui::Terminal;
|
||||||
|
|
||||||
use cli_player::server::start_server_if_missing;
|
use cli_player::server::start_server_if_missing;
|
||||||
use cli_player::ui_screens::popup_screen::PopupScreen;
|
|
||||||
use cli_player::ui_screens::*;
|
use cli_player::ui_screens::*;
|
||||||
use sea_battle_backend::data::GameRules;
|
use sea_battle_backend::data::GameRules;
|
||||||
|
|
||||||
|
/// Test code screens
|
||||||
async fn run_dev<B: Backend>(
|
async fn run_dev<B: Backend>(
|
||||||
terminal: &mut Terminal<B>,
|
terminal: &mut Terminal<B>,
|
||||||
d: TestDevScreen,
|
d: TestDevScreen,
|
||||||
) -> Result<(), Box<dyn Error>> {
|
) -> Result<(), Box<dyn Error>> {
|
||||||
let res = match d {
|
let res = match d {
|
||||||
TestDevScreen::Popup => PopupScreen::new("Welcome there!!")
|
TestDevScreen::Popup => popup_screen::PopupScreen::new("Welcome there!!")
|
||||||
.show(terminal)?
|
.show(terminal)?
|
||||||
.as_string(),
|
.as_string(),
|
||||||
TestDevScreen::Input => input_screen::InputScreen::new("Whas it your name ?")
|
TestDevScreen::Input => input_screen::InputScreen::new("What it your name ?")
|
||||||
.set_title("A custom title")
|
.set_title("A custom title")
|
||||||
.show(terminal)?
|
.show(terminal)?
|
||||||
.as_string(),
|
.as_string(),
|
||||||
|
TestDevScreen::Confirm => {
|
||||||
|
confirm_dialog::ConfirmDialogScreen::new("Do you really want to quit game?")
|
||||||
|
.show(terminal)?
|
||||||
|
.as_string()
|
||||||
|
}
|
||||||
|
TestDevScreen::SelectBotType => select_bot_type::SelectBotTypeScreen::default()
|
||||||
|
.show(terminal)?
|
||||||
|
.as_string(),
|
||||||
|
TestDevScreen::SelectPlayMode => select_play_mode::SelectPlayModeScreen::default()
|
||||||
|
.show(terminal)?
|
||||||
|
.as_string(),
|
||||||
|
TestDevScreen::SetBoatsLayout => {
|
||||||
|
let rules = GameRules {
|
||||||
|
boats_can_touch: true,
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
set_boats_layout::SetBoatsLayoutScreen::new(&rules)
|
||||||
|
.show(terminal)?
|
||||||
|
.as_string()
|
||||||
|
}
|
||||||
|
TestDevScreen::ConfigureGameRules => {
|
||||||
|
configure_game_rules::GameRulesConfigurationScreen::new(GameRules::default())
|
||||||
|
.show(terminal)?
|
||||||
|
.as_string()
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Temporary code
|
|
||||||
// let res = configure_game_rules::configure_play_rules(GameRules::default(), terminal)?; // select_bot_type::select_bot_type(terminal)?;
|
|
||||||
/*let mut rules = GameRules::default();
|
|
||||||
rules.boats_can_touch = true;
|
|
||||||
let res = set_boats_layout::set_boat_layout(&rules, terminal)?; // select_bot_type::select_bot_type(terminal)?;*/
|
|
||||||
// let res = confirm_dialog::confirm_dialog("Do you really want to interrupt game ?", terminal)?; // select_bot_type::select_bot_type(terminal)?;
|
|
||||||
// select_bot_type::select_bot_type(terminal)?;
|
|
||||||
Err(io::Error::new(
|
Err(io::Error::new(
|
||||||
ErrorKind::Other,
|
ErrorKind::Other,
|
||||||
format!("DEV result: {:?}", res),
|
format!("DEV result: {:?}", res),
|
||||||
|
@ -31,195 +31,199 @@ enum EditingField {
|
|||||||
OK,
|
OK,
|
||||||
}
|
}
|
||||||
|
|
||||||
struct GameRulesConfigurationScreen {
|
pub struct GameRulesConfigurationScreen {
|
||||||
rules: GameRules,
|
rules: GameRules,
|
||||||
curr_field: EditingField,
|
curr_field: EditingField,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn configure_play_rules<B: Backend>(
|
impl GameRulesConfigurationScreen {
|
||||||
rules: GameRules,
|
pub fn new(rules: GameRules) -> Self {
|
||||||
terminal: &mut Terminal<B>,
|
Self {
|
||||||
) -> io::Result<ScreenResult<GameRules>> {
|
rules,
|
||||||
let mut model = GameRulesConfigurationScreen {
|
curr_field: EditingField::OK,
|
||||||
rules,
|
}
|
||||||
curr_field: EditingField::OK,
|
}
|
||||||
};
|
|
||||||
|
|
||||||
let mut last_tick = Instant::now();
|
pub fn show<B: Backend>(
|
||||||
loop {
|
mut self,
|
||||||
terminal.draw(|f| ui(f, &mut model))?;
|
terminal: &mut Terminal<B>,
|
||||||
|
) -> io::Result<ScreenResult<GameRules>> {
|
||||||
|
let mut last_tick = Instant::now();
|
||||||
|
loop {
|
||||||
|
terminal.draw(|f| self.ui(f))?;
|
||||||
|
|
||||||
let timeout = TICK_RATE
|
let timeout = TICK_RATE
|
||||||
.checked_sub(last_tick.elapsed())
|
.checked_sub(last_tick.elapsed())
|
||||||
.unwrap_or_else(|| Duration::from_secs(0));
|
.unwrap_or_else(|| Duration::from_secs(0));
|
||||||
|
|
||||||
if crossterm::event::poll(timeout)? {
|
if crossterm::event::poll(timeout)? {
|
||||||
let mut cursor_pos = model.curr_field as i32;
|
let mut cursor_pos = self.curr_field as i32;
|
||||||
|
|
||||||
if let Event::Key(key) = event::read()? {
|
if let Event::Key(key) = event::read()? {
|
||||||
match key.code {
|
match key.code {
|
||||||
// Quit app
|
// Quit app
|
||||||
KeyCode::Char('q') => return Ok(ScreenResult::Canceled),
|
KeyCode::Char('q') => return Ok(ScreenResult::Canceled),
|
||||||
|
|
||||||
// Navigate between fields
|
// Navigate between fields
|
||||||
KeyCode::Up | KeyCode::Left => cursor_pos -= 1,
|
KeyCode::Up | KeyCode::Left => cursor_pos -= 1,
|
||||||
KeyCode::Down | KeyCode::Right | KeyCode::Tab => cursor_pos += 1,
|
KeyCode::Down | KeyCode::Right | KeyCode::Tab => cursor_pos += 1,
|
||||||
|
|
||||||
// Submit results
|
// Submit results
|
||||||
KeyCode::Enter => {
|
KeyCode::Enter => {
|
||||||
if model.curr_field == EditingField::Cancel {
|
if self.curr_field == EditingField::Cancel {
|
||||||
return Ok(ScreenResult::Canceled);
|
return Ok(ScreenResult::Canceled);
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.curr_field == EditingField::OK && self.rules.is_valid() {
|
||||||
|
return Ok(ScreenResult::Ok(self.rules));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if model.curr_field == EditingField::OK && model.rules.is_valid() {
|
KeyCode::Char(' ') => {
|
||||||
return Ok(ScreenResult::Ok(model.rules));
|
if self.curr_field == EditingField::BoatsCanTouch {
|
||||||
|
self.rules.boats_can_touch = !self.rules.boats_can_touch;
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.curr_field == EditingField::PlayerContinueOnHit {
|
||||||
|
self.rules.player_continue_on_hit =
|
||||||
|
!self.rules.player_continue_on_hit;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
KeyCode::Backspace => {
|
||||||
|
if self.curr_field == EditingField::MapWidth {
|
||||||
|
self.rules.map_width /= 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.curr_field == EditingField::MapHeight {
|
||||||
|
self.rules.map_height /= 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.curr_field == EditingField::BoatsList
|
||||||
|
&& !self.rules.boats_list().is_empty()
|
||||||
|
{
|
||||||
|
self.rules.remove_last_boat();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
KeyCode::Char(c) if ('0'..='9').contains(&c) => {
|
||||||
|
let val = c.to_string().parse::<usize>().unwrap_or_default();
|
||||||
|
|
||||||
|
if self.curr_field == EditingField::MapWidth {
|
||||||
|
self.rules.map_width *= 10;
|
||||||
|
self.rules.map_width += val;
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.curr_field == EditingField::MapHeight {
|
||||||
|
self.rules.map_height *= 10;
|
||||||
|
self.rules.map_height += val;
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.curr_field == EditingField::BoatsList {
|
||||||
|
self.rules.add_boat(val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_ => {}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
KeyCode::Char(' ') => {
|
// Apply new cursor position
|
||||||
if model.curr_field == EditingField::BoatsCanTouch {
|
self.curr_field = if cursor_pos < 0 {
|
||||||
model.rules.boats_can_touch = !model.rules.boats_can_touch;
|
EditingField::OK
|
||||||
}
|
} else {
|
||||||
|
num_renamed::FromPrimitive::from_u64(cursor_pos as u64)
|
||||||
if model.curr_field == EditingField::PlayerContinueOnHit {
|
.unwrap_or(EditingField::MapWidth)
|
||||||
model.rules.player_continue_on_hit =
|
|
||||||
!model.rules.player_continue_on_hit;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
KeyCode::Backspace => {
|
|
||||||
if model.curr_field == EditingField::MapWidth {
|
|
||||||
model.rules.map_width /= 10;
|
|
||||||
}
|
|
||||||
|
|
||||||
if model.curr_field == EditingField::MapHeight {
|
|
||||||
model.rules.map_height /= 10;
|
|
||||||
}
|
|
||||||
|
|
||||||
if model.curr_field == EditingField::BoatsList
|
|
||||||
&& !model.rules.boats_list().is_empty()
|
|
||||||
{
|
|
||||||
model.rules.remove_last_boat();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
KeyCode::Char(c) if ('0'..='9').contains(&c) => {
|
|
||||||
let val = c.to_string().parse::<usize>().unwrap_or_default();
|
|
||||||
|
|
||||||
if model.curr_field == EditingField::MapWidth {
|
|
||||||
model.rules.map_width *= 10;
|
|
||||||
model.rules.map_width += val;
|
|
||||||
}
|
|
||||||
|
|
||||||
if model.curr_field == EditingField::MapHeight {
|
|
||||||
model.rules.map_height *= 10;
|
|
||||||
model.rules.map_height += val;
|
|
||||||
}
|
|
||||||
|
|
||||||
if model.curr_field == EditingField::BoatsList {
|
|
||||||
model.rules.add_boat(val);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_ => {}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if last_tick.elapsed() >= TICK_RATE {
|
||||||
// Apply new cursor position
|
last_tick = Instant::now();
|
||||||
model.curr_field = if cursor_pos < 0 {
|
|
||||||
EditingField::OK
|
|
||||||
} else {
|
|
||||||
num_renamed::FromPrimitive::from_u64(cursor_pos as u64)
|
|
||||||
.unwrap_or(EditingField::MapWidth)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if last_tick.elapsed() >= TICK_RATE {
|
}
|
||||||
last_tick = Instant::now();
|
|
||||||
|
fn ui<B: Backend>(&mut self, f: &mut Frame<B>) {
|
||||||
|
let area = centered_rect_size(50, 16, &f.size());
|
||||||
|
|
||||||
|
let block = Block::default().title("Game rules").borders(Borders::ALL);
|
||||||
|
f.render_widget(block, area);
|
||||||
|
|
||||||
|
let chunks = Layout::default()
|
||||||
|
.direction(Direction::Vertical)
|
||||||
|
.constraints([
|
||||||
|
Constraint::Length(3),
|
||||||
|
Constraint::Length(3),
|
||||||
|
Constraint::Length(3),
|
||||||
|
Constraint::Length(1),
|
||||||
|
Constraint::Length(1),
|
||||||
|
Constraint::Length(1),
|
||||||
|
Constraint::Length(1), // Buttons
|
||||||
|
Constraint::Length(1), // Error message (if any)
|
||||||
|
])
|
||||||
|
.split(area.inner(&Margin {
|
||||||
|
horizontal: 2,
|
||||||
|
vertical: 1,
|
||||||
|
}));
|
||||||
|
|
||||||
|
let editor = TextEditorWidget::new(
|
||||||
|
"Map width",
|
||||||
|
&self.rules.map_width.to_string(),
|
||||||
|
self.curr_field == EditingField::MapWidth,
|
||||||
|
);
|
||||||
|
f.render_widget(editor, chunks[EditingField::MapWidth as usize]);
|
||||||
|
|
||||||
|
let editor = TextEditorWidget::new(
|
||||||
|
"Map height",
|
||||||
|
&self.rules.map_height.to_string(),
|
||||||
|
self.curr_field == EditingField::MapHeight,
|
||||||
|
);
|
||||||
|
f.render_widget(editor, chunks[EditingField::MapHeight as usize]);
|
||||||
|
|
||||||
|
let editor = TextEditorWidget::new(
|
||||||
|
"Boats list",
|
||||||
|
&self
|
||||||
|
.rules
|
||||||
|
.boats_list()
|
||||||
|
.iter()
|
||||||
|
.map(usize::to_string)
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.join("; "),
|
||||||
|
self.curr_field == EditingField::BoatsList,
|
||||||
|
);
|
||||||
|
f.render_widget(editor, chunks[EditingField::BoatsList as usize]);
|
||||||
|
|
||||||
|
let editor = CheckboxWidget::new(
|
||||||
|
"Boats can touch",
|
||||||
|
self.rules.boats_can_touch,
|
||||||
|
self.curr_field == EditingField::BoatsCanTouch,
|
||||||
|
);
|
||||||
|
f.render_widget(editor, chunks[EditingField::BoatsCanTouch as usize]);
|
||||||
|
|
||||||
|
let editor = CheckboxWidget::new(
|
||||||
|
"Player continue on hit",
|
||||||
|
self.rules.player_continue_on_hit,
|
||||||
|
self.curr_field == EditingField::PlayerContinueOnHit,
|
||||||
|
);
|
||||||
|
f.render_widget(editor, chunks[EditingField::PlayerContinueOnHit as usize]);
|
||||||
|
|
||||||
|
// Buttons
|
||||||
|
let buttons_chunk = Layout::default()
|
||||||
|
.direction(Direction::Horizontal)
|
||||||
|
.constraints([Constraint::Percentage(50), Constraint::Percentage(50)])
|
||||||
|
.split(chunks[EditingField::OK as usize]);
|
||||||
|
|
||||||
|
let button = ButtonWidget::new("Cancel", self.curr_field == EditingField::Cancel);
|
||||||
|
f.render_widget(button, buttons_chunk[0]);
|
||||||
|
|
||||||
|
let button = ButtonWidget::new("OK", self.curr_field == EditingField::OK)
|
||||||
|
.set_disabled(!self.rules.is_valid());
|
||||||
|
f.render_widget(button, buttons_chunk[1]);
|
||||||
|
|
||||||
|
// Error message (if any)
|
||||||
|
if let Some(msg) = self.rules.get_errors().first() {
|
||||||
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn ui<B: Backend>(f: &mut Frame<B>, model: &mut GameRulesConfigurationScreen) {
|
|
||||||
let area = centered_rect_size(50, 16, &f.size());
|
|
||||||
|
|
||||||
let block = Block::default().title("Game rules").borders(Borders::ALL);
|
|
||||||
f.render_widget(block, area);
|
|
||||||
|
|
||||||
let chunks = Layout::default()
|
|
||||||
.direction(Direction::Vertical)
|
|
||||||
.constraints([
|
|
||||||
Constraint::Length(3),
|
|
||||||
Constraint::Length(3),
|
|
||||||
Constraint::Length(3),
|
|
||||||
Constraint::Length(1),
|
|
||||||
Constraint::Length(1),
|
|
||||||
Constraint::Length(1),
|
|
||||||
Constraint::Length(1), // Buttons
|
|
||||||
Constraint::Length(1), // Error message (if any)
|
|
||||||
])
|
|
||||||
.split(area.inner(&Margin {
|
|
||||||
horizontal: 2,
|
|
||||||
vertical: 1,
|
|
||||||
}));
|
|
||||||
|
|
||||||
let editor = TextEditorWidget::new(
|
|
||||||
"Map width",
|
|
||||||
&model.rules.map_width.to_string(),
|
|
||||||
model.curr_field == EditingField::MapWidth,
|
|
||||||
);
|
|
||||||
f.render_widget(editor, chunks[EditingField::MapWidth as usize]);
|
|
||||||
|
|
||||||
let editor = TextEditorWidget::new(
|
|
||||||
"Map height",
|
|
||||||
&model.rules.map_height.to_string(),
|
|
||||||
model.curr_field == EditingField::MapHeight,
|
|
||||||
);
|
|
||||||
f.render_widget(editor, chunks[EditingField::MapHeight as usize]);
|
|
||||||
|
|
||||||
let editor = TextEditorWidget::new(
|
|
||||||
"Boats list",
|
|
||||||
&model
|
|
||||||
.rules
|
|
||||||
.boats_list()
|
|
||||||
.iter()
|
|
||||||
.map(usize::to_string)
|
|
||||||
.collect::<Vec<_>>()
|
|
||||||
.join("; "),
|
|
||||||
model.curr_field == EditingField::BoatsList,
|
|
||||||
);
|
|
||||||
f.render_widget(editor, chunks[EditingField::BoatsList as usize]);
|
|
||||||
|
|
||||||
let editor = CheckboxWidget::new(
|
|
||||||
"Boats can touch",
|
|
||||||
model.rules.boats_can_touch,
|
|
||||||
model.curr_field == EditingField::BoatsCanTouch,
|
|
||||||
);
|
|
||||||
f.render_widget(editor, chunks[EditingField::BoatsCanTouch as usize]);
|
|
||||||
|
|
||||||
let editor = CheckboxWidget::new(
|
|
||||||
"Player continue on hit",
|
|
||||||
model.rules.player_continue_on_hit,
|
|
||||||
model.curr_field == EditingField::PlayerContinueOnHit,
|
|
||||||
);
|
|
||||||
f.render_widget(editor, chunks[EditingField::PlayerContinueOnHit as usize]);
|
|
||||||
|
|
||||||
// Buttons
|
|
||||||
let buttons_chunk = Layout::default()
|
|
||||||
.direction(Direction::Horizontal)
|
|
||||||
.constraints([Constraint::Percentage(50), Constraint::Percentage(50)])
|
|
||||||
.split(chunks[EditingField::OK as usize]);
|
|
||||||
|
|
||||||
let button = ButtonWidget::new("Cancel", model.curr_field == EditingField::Cancel);
|
|
||||||
f.render_widget(button, buttons_chunk[0]);
|
|
||||||
|
|
||||||
let button = ButtonWidget::new("OK", model.curr_field == EditingField::OK)
|
|
||||||
.set_disabled(!model.rules.is_valid());
|
|
||||||
f.render_widget(button, buttons_chunk[1]);
|
|
||||||
|
|
||||||
// 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 err = Paragraph::new(*msg).style(Style::default().fg(Color::Red));
|
|
||||||
f.render_widget(err, area);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -14,99 +14,103 @@ use crate::ui_screens::utils::centered_rect_size;
|
|||||||
use crate::ui_screens::ScreenResult;
|
use crate::ui_screens::ScreenResult;
|
||||||
use crate::ui_widgets::button_widget::ButtonWidget;
|
use crate::ui_widgets::button_widget::ButtonWidget;
|
||||||
|
|
||||||
struct ConfirmDialogScreen<'a> {
|
pub struct ConfirmDialogScreen<'a> {
|
||||||
title: &'a str,
|
title: &'a str,
|
||||||
msg: &'a str,
|
msg: &'a str,
|
||||||
is_confirm: bool,
|
is_confirm: bool,
|
||||||
can_cancel: bool,
|
can_cancel: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn confirm_dialog<B: Backend>(
|
impl<'a> ConfirmDialogScreen<'a> {
|
||||||
msg: &str,
|
pub fn new(msg: &'a str) -> Self {
|
||||||
terminal: &mut Terminal<B>,
|
Self {
|
||||||
) -> io::Result<ScreenResult<bool>> {
|
title: "Confirmation Request",
|
||||||
let mut model = ConfirmDialogScreen {
|
msg,
|
||||||
title: "Confirmation Request",
|
is_confirm: true,
|
||||||
msg,
|
can_cancel: false,
|
||||||
is_confirm: true,
|
|
||||||
can_cancel: false,
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut last_tick = Instant::now();
|
|
||||||
loop {
|
|
||||||
terminal.draw(|f| ui(f, &mut model))?;
|
|
||||||
|
|
||||||
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::Esc | KeyCode::Char('q') if model.can_cancel => {
|
|
||||||
return Ok(ScreenResult::Canceled)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Toggle selected choice
|
|
||||||
KeyCode::Left | KeyCode::Right | KeyCode::Tab => {
|
|
||||||
model.is_confirm = !model.is_confirm
|
|
||||||
}
|
|
||||||
|
|
||||||
// Submit choice
|
|
||||||
KeyCode::Enter => {
|
|
||||||
return Ok(ScreenResult::Ok(model.is_confirm));
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if last_tick.elapsed() >= TICK_RATE {
|
|
||||||
last_tick = Instant::now();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
pub fn show<B: Backend>(
|
||||||
fn ui<B: Backend>(f: &mut Frame<B>, model: &mut ConfirmDialogScreen) {
|
mut self,
|
||||||
// Preprocess message
|
terminal: &mut Terminal<B>,
|
||||||
let lines = textwrap::wrap(model.msg, f.size().width as usize - 20);
|
) -> io::Result<ScreenResult<bool>> {
|
||||||
let line_max_len = lines.iter().map(|l| l.len()).max().unwrap();
|
let mut last_tick = Instant::now();
|
||||||
|
loop {
|
||||||
let area = centered_rect_size(line_max_len as u16 + 4, 5 + lines.len() as u16, &f.size());
|
terminal.draw(|f| self.ui(f))?;
|
||||||
|
|
||||||
let block = Block::default().borders(Borders::ALL).title(model.title);
|
let timeout = TICK_RATE
|
||||||
f.render_widget(block, area);
|
.checked_sub(last_tick.elapsed())
|
||||||
|
.unwrap_or_else(|| Duration::from_secs(0));
|
||||||
// Create two chunks with equal horizontal screen space
|
|
||||||
let chunks = Layout::default()
|
if event::poll(timeout)? {
|
||||||
.direction(Direction::Vertical)
|
if let Event::Key(key) = event::read()? {
|
||||||
.constraints(
|
match key.code {
|
||||||
[
|
KeyCode::Esc | KeyCode::Char('q') if self.can_cancel => {
|
||||||
Constraint::Length(lines.len() as u16),
|
return Ok(ScreenResult::Canceled)
|
||||||
Constraint::Length(3),
|
}
|
||||||
]
|
|
||||||
.as_ref(),
|
// Toggle selected choice
|
||||||
)
|
KeyCode::Left | KeyCode::Right | KeyCode::Tab => {
|
||||||
.split(area.inner(&Margin {
|
self.is_confirm = !self.is_confirm
|
||||||
horizontal: 2,
|
}
|
||||||
vertical: 1,
|
|
||||||
}));
|
// Submit choice
|
||||||
|
KeyCode::Enter => {
|
||||||
let text = lines
|
return Ok(ScreenResult::Ok(self.is_confirm));
|
||||||
.iter()
|
}
|
||||||
.map(|s| Spans::from(s.as_ref()))
|
_ => {}
|
||||||
.collect::<Vec<_>>();
|
}
|
||||||
let paragraph = Paragraph::new(text);
|
}
|
||||||
f.render_widget(paragraph, chunks[0]);
|
}
|
||||||
|
if last_tick.elapsed() >= TICK_RATE {
|
||||||
// Buttons
|
last_tick = Instant::now();
|
||||||
let buttons_area = Layout::default()
|
}
|
||||||
.direction(Direction::Horizontal)
|
}
|
||||||
.constraints([Constraint::Percentage(50), Constraint::Percentage(50)].as_ref())
|
}
|
||||||
.split(chunks[1]);
|
|
||||||
|
fn ui<B: Backend>(&mut self, f: &mut Frame<B>) {
|
||||||
let cancel_button = ButtonWidget::new("Cancel", true).set_disabled(model.is_confirm);
|
// Preprocess message
|
||||||
f.render_widget(cancel_button, buttons_area[0]);
|
let lines = textwrap::wrap(self.msg, f.size().width as usize - 20);
|
||||||
|
let line_max_len = lines.iter().map(|l| l.len()).max().unwrap();
|
||||||
let ok_button = ButtonWidget::new("Confirm", true).set_disabled(!model.is_confirm);
|
|
||||||
f.render_widget(ok_button, buttons_area[1]);
|
let area = centered_rect_size(line_max_len as u16 + 4, 5 + lines.len() as u16, &f.size());
|
||||||
|
|
||||||
|
let block = Block::default().borders(Borders::ALL).title(self.title);
|
||||||
|
f.render_widget(block, area);
|
||||||
|
|
||||||
|
// Create two chunks with equal horizontal screen space
|
||||||
|
let chunks = Layout::default()
|
||||||
|
.direction(Direction::Vertical)
|
||||||
|
.constraints(
|
||||||
|
[
|
||||||
|
Constraint::Length(lines.len() as u16),
|
||||||
|
Constraint::Length(3),
|
||||||
|
]
|
||||||
|
.as_ref(),
|
||||||
|
)
|
||||||
|
.split(area.inner(&Margin {
|
||||||
|
horizontal: 2,
|
||||||
|
vertical: 1,
|
||||||
|
}));
|
||||||
|
|
||||||
|
let text = lines
|
||||||
|
.iter()
|
||||||
|
.map(|s| Spans::from(s.as_ref()))
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
let paragraph = Paragraph::new(text);
|
||||||
|
f.render_widget(paragraph, chunks[0]);
|
||||||
|
|
||||||
|
// Buttons
|
||||||
|
let buttons_area = Layout::default()
|
||||||
|
.direction(Direction::Horizontal)
|
||||||
|
.constraints([Constraint::Percentage(50), Constraint::Percentage(50)].as_ref())
|
||||||
|
.split(chunks[1]);
|
||||||
|
|
||||||
|
let cancel_button = ButtonWidget::new("Cancel", true).set_disabled(self.is_confirm);
|
||||||
|
f.render_widget(cancel_button, buttons_area[0]);
|
||||||
|
|
||||||
|
let ok_button = ButtonWidget::new("Confirm", true).set_disabled(!self.is_confirm);
|
||||||
|
f.render_widget(ok_button, buttons_area[1]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,81 +15,88 @@ use crate::constants::{HIGHLIGHT_COLOR, TICK_RATE};
|
|||||||
use crate::ui_screens::utils::centered_rect_size;
|
use crate::ui_screens::utils::centered_rect_size;
|
||||||
use crate::ui_screens::ScreenResult;
|
use crate::ui_screens::ScreenResult;
|
||||||
|
|
||||||
struct SelectPlayModeScreen {
|
pub struct SelectBotTypeScreen {
|
||||||
state: ListState,
|
state: ListState,
|
||||||
curr_selection: usize,
|
curr_selection: usize,
|
||||||
types: Vec<BotDescription>,
|
types: Vec<BotDescription>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn select_bot_type<B: Backend>(
|
impl Default for SelectBotTypeScreen {
|
||||||
terminal: &mut Terminal<B>,
|
fn default() -> Self {
|
||||||
) -> io::Result<ScreenResult<BotType>> {
|
let types = PlayConfiguration::default().bot_types;
|
||||||
let types = PlayConfiguration::default().bot_types;
|
Self {
|
||||||
let mut model = SelectPlayModeScreen {
|
state: Default::default(),
|
||||||
state: Default::default(),
|
curr_selection: types.len() - 1,
|
||||||
curr_selection: types.len() - 1,
|
types,
|
||||||
types,
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut last_tick = Instant::now();
|
|
||||||
loop {
|
|
||||||
model.state.select(Some(model.curr_selection));
|
|
||||||
terminal.draw(|f| ui(f, &mut model))?;
|
|
||||||
|
|
||||||
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 => {
|
|
||||||
return Ok(ScreenResult::Ok(model.types[model.curr_selection].r#type));
|
|
||||||
}
|
|
||||||
KeyCode::Down => model.curr_selection += 1,
|
|
||||||
KeyCode::Up => model.curr_selection += model.types.len() - 1,
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
|
|
||||||
model.curr_selection %= model.types.len();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if last_tick.elapsed() >= TICK_RATE {
|
|
||||||
last_tick = Instant::now();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn ui<B: Backend>(f: &mut Frame<B>, model: &mut SelectPlayModeScreen) {
|
impl SelectBotTypeScreen {
|
||||||
let area = centered_rect_size(60, model.types.len() as u16 * 2 + 2, &f.size());
|
pub fn show<B: Backend>(
|
||||||
|
mut self,
|
||||||
|
terminal: &mut Terminal<B>,
|
||||||
|
) -> io::Result<ScreenResult<BotType>> {
|
||||||
|
let mut last_tick = Instant::now();
|
||||||
|
loop {
|
||||||
|
self.state.select(Some(self.curr_selection));
|
||||||
|
terminal.draw(|f| self.ui(f))?;
|
||||||
|
|
||||||
// Create a List from all list items and highlight the currently selected one
|
let timeout = TICK_RATE
|
||||||
let items = model
|
.checked_sub(last_tick.elapsed())
|
||||||
.types
|
.unwrap_or_else(|| Duration::from_secs(0));
|
||||||
.iter()
|
|
||||||
.map(|bot| {
|
|
||||||
ListItem::new(vec![
|
|
||||||
Spans::from(bot.name),
|
|
||||||
Spans::from(Span::styled(
|
|
||||||
bot.description,
|
|
||||||
Style::default().add_modifier(Modifier::ITALIC),
|
|
||||||
)),
|
|
||||||
])
|
|
||||||
})
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
let items = List::new(items)
|
|
||||||
.block(
|
|
||||||
Block::default()
|
|
||||||
.title("Select bot type")
|
|
||||||
.borders(Borders::ALL),
|
|
||||||
)
|
|
||||||
.highlight_style(
|
|
||||||
Style::default()
|
|
||||||
.fg(HIGHLIGHT_COLOR)
|
|
||||||
.add_modifier(Modifier::BOLD),
|
|
||||||
)
|
|
||||||
.highlight_symbol(">> ");
|
|
||||||
|
|
||||||
f.render_stateful_widget(items, area, &mut model.state);
|
if event::poll(timeout)? {
|
||||||
|
if let Event::Key(key) = event::read()? {
|
||||||
|
match key.code {
|
||||||
|
KeyCode::Char('q') => return Ok(ScreenResult::Canceled),
|
||||||
|
KeyCode::Enter => {
|
||||||
|
return Ok(ScreenResult::Ok(self.types[self.curr_selection].r#type));
|
||||||
|
}
|
||||||
|
KeyCode::Down => self.curr_selection += 1,
|
||||||
|
KeyCode::Up => self.curr_selection += self.types.len() - 1,
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.curr_selection %= self.types.len();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if last_tick.elapsed() >= TICK_RATE {
|
||||||
|
last_tick = Instant::now();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn ui<B: Backend>(&mut self, f: &mut Frame<B>) {
|
||||||
|
let area = centered_rect_size(60, self.types.len() as u16 * 2 + 2, &f.size());
|
||||||
|
|
||||||
|
// Create a List from all list items and highlight the currently selected one
|
||||||
|
let items = self
|
||||||
|
.types
|
||||||
|
.iter()
|
||||||
|
.map(|bot| {
|
||||||
|
ListItem::new(vec![
|
||||||
|
Spans::from(bot.name),
|
||||||
|
Spans::from(Span::styled(
|
||||||
|
bot.description,
|
||||||
|
Style::default().add_modifier(Modifier::ITALIC),
|
||||||
|
)),
|
||||||
|
])
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
let items = List::new(items)
|
||||||
|
.block(
|
||||||
|
Block::default()
|
||||||
|
.title("Select bot type")
|
||||||
|
.borders(Borders::ALL),
|
||||||
|
)
|
||||||
|
.highlight_style(
|
||||||
|
Style::default()
|
||||||
|
.fg(HIGHLIGHT_COLOR)
|
||||||
|
.add_modifier(Modifier::BOLD),
|
||||||
|
)
|
||||||
|
.highlight_symbol(">> ");
|
||||||
|
|
||||||
|
f.render_stateful_widget(items, area, &mut self.state);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@ use std::time::{Duration, Instant};
|
|||||||
|
|
||||||
use crate::constants::{HIGHLIGHT_COLOR, TICK_RATE};
|
use crate::constants::{HIGHLIGHT_COLOR, TICK_RATE};
|
||||||
use crate::ui_screens::utils::centered_rect_size;
|
use crate::ui_screens::utils::centered_rect_size;
|
||||||
|
use crate::ui_screens::ScreenResult;
|
||||||
use crossterm::event;
|
use crossterm::event;
|
||||||
use crossterm::event::{Event, KeyCode};
|
use crossterm::event::{Event, KeyCode};
|
||||||
use tui::backend::Backend;
|
use tui::backend::Backend;
|
||||||
@ -41,64 +42,69 @@ const AVAILABLE_PLAY_MODES: [PlayModeDescription; 3] = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
struct SelectPlayModeScreen {
|
pub struct SelectPlayModeScreen {
|
||||||
state: ListState,
|
state: ListState,
|
||||||
curr_selection: usize,
|
curr_selection: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn select_play_mode<B: Backend>(
|
impl SelectPlayModeScreen {
|
||||||
terminal: &mut Terminal<B>,
|
pub fn show<B: Backend>(
|
||||||
) -> io::Result<SelectPlayModeResult> {
|
mut self,
|
||||||
let mut model = SelectPlayModeScreen::default();
|
terminal: &mut Terminal<B>,
|
||||||
|
) -> io::Result<ScreenResult<SelectPlayModeResult>> {
|
||||||
|
let mut last_tick = Instant::now();
|
||||||
|
loop {
|
||||||
|
self.state.select(Some(self.curr_selection));
|
||||||
|
terminal.draw(|f| self.ui(f))?;
|
||||||
|
|
||||||
let mut last_tick = Instant::now();
|
let timeout = TICK_RATE
|
||||||
loop {
|
.checked_sub(last_tick.elapsed())
|
||||||
model.state.select(Some(model.curr_selection));
|
.unwrap_or_else(|| Duration::from_secs(0));
|
||||||
terminal.draw(|f| ui(f, &mut model))?;
|
|
||||||
|
|
||||||
let timeout = TICK_RATE
|
if crossterm::event::poll(timeout)? {
|
||||||
.checked_sub(last_tick.elapsed())
|
if let Event::Key(key) = event::read()? {
|
||||||
.unwrap_or_else(|| Duration::from_secs(0));
|
match key.code {
|
||||||
|
KeyCode::Char('q') => return Ok(ScreenResult::Canceled),
|
||||||
|
KeyCode::Enter => {
|
||||||
|
return Ok(ScreenResult::Ok(
|
||||||
|
AVAILABLE_PLAY_MODES[self.curr_selection].value,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
KeyCode::Down => self.curr_selection += 1,
|
||||||
|
KeyCode::Up => self.curr_selection += AVAILABLE_PLAY_MODES.len() - 1,
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
|
||||||
if crossterm::event::poll(timeout)? {
|
self.curr_selection %= AVAILABLE_PLAY_MODES.len();
|
||||||
if let Event::Key(key) = event::read()? {
|
|
||||||
match key.code {
|
|
||||||
KeyCode::Char('q') => return Ok(SelectPlayModeResult::Exit),
|
|
||||||
KeyCode::Enter => return Ok(AVAILABLE_PLAY_MODES[model.curr_selection].value),
|
|
||||||
KeyCode::Down => model.curr_selection += 1,
|
|
||||||
KeyCode::Up => model.curr_selection += AVAILABLE_PLAY_MODES.len() - 1,
|
|
||||||
_ => {}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
model.curr_selection %= AVAILABLE_PLAY_MODES.len();
|
if last_tick.elapsed() >= TICK_RATE {
|
||||||
|
last_tick = Instant::now();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if last_tick.elapsed() >= TICK_RATE {
|
}
|
||||||
last_tick = Instant::now();
|
|
||||||
}
|
fn ui<B: Backend>(&mut self, f: &mut Frame<B>) {
|
||||||
|
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
|
||||||
|
.iter()
|
||||||
|
.map(|mode| ListItem::new(Text::raw(mode.name)))
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
let items = List::new(items)
|
||||||
|
.block(
|
||||||
|
Block::default()
|
||||||
|
.title("Select play mode")
|
||||||
|
.borders(Borders::ALL),
|
||||||
|
)
|
||||||
|
.highlight_style(
|
||||||
|
Style::default()
|
||||||
|
.fg(HIGHLIGHT_COLOR)
|
||||||
|
.add_modifier(Modifier::BOLD),
|
||||||
|
)
|
||||||
|
.highlight_symbol(">> ");
|
||||||
|
|
||||||
|
f.render_stateful_widget(items, area, &mut self.state);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn ui<B: Backend>(f: &mut Frame<B>, model: &mut SelectPlayModeScreen) {
|
|
||||||
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
|
|
||||||
.iter()
|
|
||||||
.map(|mode| ListItem::new(Text::raw(mode.name)))
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
let items = List::new(items)
|
|
||||||
.block(
|
|
||||||
Block::default()
|
|
||||||
.title("Select play mode")
|
|
||||||
.borders(Borders::ALL),
|
|
||||||
)
|
|
||||||
.highlight_style(
|
|
||||||
Style::default()
|
|
||||||
.fg(HIGHLIGHT_COLOR)
|
|
||||||
.add_modifier(Modifier::BOLD),
|
|
||||||
)
|
|
||||||
.highlight_symbol(">> ");
|
|
||||||
|
|
||||||
f.render_stateful_widget(items, area, &mut model.state);
|
|
||||||
}
|
|
||||||
|
@ -17,216 +17,218 @@ use crate::ui_screens::utils::{centered_rect_size, centered_text};
|
|||||||
use crate::ui_screens::ScreenResult;
|
use crate::ui_screens::ScreenResult;
|
||||||
use crate::ui_widgets::game_map_widget::{ColoredCells, GameMapWidget};
|
use crate::ui_widgets::game_map_widget::{ColoredCells, GameMapWidget};
|
||||||
|
|
||||||
struct SetBotsLayoutScreen {
|
|
||||||
curr_boat: usize,
|
|
||||||
layout: BoatsLayout,
|
|
||||||
}
|
|
||||||
|
|
||||||
type CoordinatesMapper = HashMap<Coordinates, Coordinates>;
|
type CoordinatesMapper = HashMap<Coordinates, Coordinates>;
|
||||||
|
|
||||||
pub fn set_boat_layout<B: Backend>(
|
pub struct SetBoatsLayoutScreen<'a> {
|
||||||
rules: &GameRules,
|
curr_boat: usize,
|
||||||
terminal: &mut Terminal<B>,
|
layout: BoatsLayout,
|
||||||
) -> io::Result<ScreenResult<BoatsLayout>> {
|
rules: &'a GameRules,
|
||||||
let mut model = SetBotsLayoutScreen {
|
|
||||||
curr_boat: 0,
|
|
||||||
layout: BoatsLayout::gen_random_for_rules(rules)
|
|
||||||
.expect("Failed to generate initial boats layout"),
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut coordinates_mapper = CoordinatesMapper::default();
|
|
||||||
|
|
||||||
let mut last_tick = Instant::now();
|
|
||||||
let mut is_moving_boat = false;
|
|
||||||
loop {
|
|
||||||
terminal.draw(|f| coordinates_mapper = 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)? {
|
|
||||||
let mut move_boat = None;
|
|
||||||
|
|
||||||
let event = event::read()?;
|
|
||||||
if let Event::Key(key) = &event {
|
|
||||||
match key.code {
|
|
||||||
KeyCode::Char('q') => return Ok(ScreenResult::Canceled),
|
|
||||||
|
|
||||||
// Select next boat
|
|
||||||
KeyCode::Char('n') => model.curr_boat += model.layout.number_of_boats() - 1,
|
|
||||||
|
|
||||||
// Rotate boat
|
|
||||||
KeyCode::Char('r') => {
|
|
||||||
model.layout.0[model.curr_boat].direction =
|
|
||||||
match model.layout.0[model.curr_boat].direction {
|
|
||||||
BoatDirection::Right => BoatDirection::Down,
|
|
||||||
_ => BoatDirection::Right,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Move boat
|
|
||||||
KeyCode::Left => move_boat = Some((-1, 0)),
|
|
||||||
KeyCode::Right => move_boat = Some((1, 0)),
|
|
||||||
KeyCode::Up => move_boat = Some((0, -1)),
|
|
||||||
KeyCode::Down => move_boat = Some((0, 1)),
|
|
||||||
|
|
||||||
// Submit configuration
|
|
||||||
KeyCode::Enter => {
|
|
||||||
if model.layout.is_valid(rules) {
|
|
||||||
return Ok(ScreenResult::Ok(model.layout));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
|
|
||||||
model.curr_boat %= model.layout.number_of_boats();
|
|
||||||
|
|
||||||
// Apply boat move
|
|
||||||
if let Some((x, y)) = move_boat {
|
|
||||||
let new_pos = model.layout.0[model.curr_boat].start.add_x(x).add_y(y);
|
|
||||||
if new_pos.is_valid(rules) {
|
|
||||||
model.layout.0[model.curr_boat].start = new_pos;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Mouse event
|
|
||||||
else if let Event::Mouse(mouse) = event {
|
|
||||||
let src_pos = Coordinates::new(mouse.column, mouse.row);
|
|
||||||
|
|
||||||
// Start mouse action
|
|
||||||
if MouseEventKind::Down(MouseButton::Left) == mouse.kind {
|
|
||||||
is_moving_boat = if let Some(pos) = coordinates_mapper.get(&src_pos) {
|
|
||||||
if let Some(b) = model.layout.find_boat_at_position(*pos) {
|
|
||||||
model.curr_boat = model.layout.0.iter().position(|s| s == b).unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
true
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Handle continue mouse action
|
|
||||||
else if is_moving_boat {
|
|
||||||
if let Some(pos) = coordinates_mapper.get(&src_pos) {
|
|
||||||
model.layout.0[model.curr_boat].start = *pos;
|
|
||||||
}
|
|
||||||
|
|
||||||
if let MouseEventKind::Up(_) = mouse.kind {
|
|
||||||
is_moving_boat = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if last_tick.elapsed() >= TICK_RATE {
|
|
||||||
last_tick = Instant::now();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn ui<B: Backend>(
|
impl<'a> SetBoatsLayoutScreen<'a> {
|
||||||
f: &mut Frame<B>,
|
pub fn new(rules: &'a GameRules) -> Self {
|
||||||
model: &mut SetBotsLayoutScreen,
|
Self {
|
||||||
rules: &GameRules,
|
curr_boat: 0,
|
||||||
) -> CoordinatesMapper {
|
layout: BoatsLayout::gen_random_for_rules(rules)
|
||||||
let errors = model.layout.errors(rules);
|
.expect("Failed to generate initial boats layout"),
|
||||||
|
rules,
|
||||||
// Color of current boat
|
|
||||||
let current_boat = ColoredCells {
|
|
||||||
color: Color::Green,
|
|
||||||
cells: model.layout.0[model.curr_boat].all_coordinates(),
|
|
||||||
};
|
|
||||||
|
|
||||||
// Color of invalid boats
|
|
||||||
let mut invalid_coordinates = vec![];
|
|
||||||
for (idx, pos) in model.layout.boats().iter().enumerate() {
|
|
||||||
if idx == model.curr_boat {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if !model
|
|
||||||
.layout
|
|
||||||
.check_present_boat_position(idx, rules)
|
|
||||||
.is_empty()
|
|
||||||
{
|
|
||||||
invalid_coordinates.append(&mut pos.all_coordinates());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let invalid_boats = ColoredCells {
|
|
||||||
color: Color::Red,
|
|
||||||
cells: invalid_coordinates,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Color of other boats
|
pub fn show<B: Backend>(
|
||||||
let mut other_boats_cells = vec![];
|
mut self,
|
||||||
for boat in &model.layout.0 {
|
terminal: &mut Terminal<B>,
|
||||||
other_boats_cells.append(&mut boat.all_coordinates());
|
) -> io::Result<ScreenResult<BoatsLayout>> {
|
||||||
|
let mut coordinates_mapper = CoordinatesMapper::default();
|
||||||
|
|
||||||
|
let mut last_tick = Instant::now();
|
||||||
|
let mut is_moving_boat = false;
|
||||||
|
loop {
|
||||||
|
terminal.draw(|f| coordinates_mapper = self.ui(f))?;
|
||||||
|
|
||||||
|
let timeout = TICK_RATE
|
||||||
|
.checked_sub(last_tick.elapsed())
|
||||||
|
.unwrap_or_else(|| Duration::from_secs(0));
|
||||||
|
|
||||||
|
if event::poll(timeout)? {
|
||||||
|
let mut move_boat = None;
|
||||||
|
|
||||||
|
let event = event::read()?;
|
||||||
|
if let Event::Key(key) = &event {
|
||||||
|
match key.code {
|
||||||
|
KeyCode::Char('q') => return Ok(ScreenResult::Canceled),
|
||||||
|
|
||||||
|
// Select next boat
|
||||||
|
KeyCode::Char('n') => self.curr_boat += self.layout.number_of_boats() - 1,
|
||||||
|
|
||||||
|
// Rotate boat
|
||||||
|
KeyCode::Char('r') => {
|
||||||
|
self.layout.0[self.curr_boat].direction =
|
||||||
|
match self.layout.0[self.curr_boat].direction {
|
||||||
|
BoatDirection::Right => BoatDirection::Down,
|
||||||
|
_ => BoatDirection::Right,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Move boat
|
||||||
|
KeyCode::Left => move_boat = Some((-1, 0)),
|
||||||
|
KeyCode::Right => move_boat = Some((1, 0)),
|
||||||
|
KeyCode::Up => move_boat = Some((0, -1)),
|
||||||
|
KeyCode::Down => move_boat = Some((0, 1)),
|
||||||
|
|
||||||
|
// Submit configuration
|
||||||
|
KeyCode::Enter => {
|
||||||
|
if self.layout.is_valid(self.rules) {
|
||||||
|
return Ok(ScreenResult::Ok(self.layout));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.curr_boat %= self.layout.number_of_boats();
|
||||||
|
|
||||||
|
// Apply boat move
|
||||||
|
if let Some((x, y)) = move_boat {
|
||||||
|
let new_pos = self.layout.0[self.curr_boat].start.add_x(x).add_y(y);
|
||||||
|
if new_pos.is_valid(self.rules) {
|
||||||
|
self.layout.0[self.curr_boat].start = new_pos;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Mouse event
|
||||||
|
else if let Event::Mouse(mouse) = event {
|
||||||
|
let src_pos = Coordinates::new(mouse.column, mouse.row);
|
||||||
|
|
||||||
|
// Start mouse action
|
||||||
|
if MouseEventKind::Down(MouseButton::Left) == mouse.kind {
|
||||||
|
is_moving_boat = if let Some(pos) = coordinates_mapper.get(&src_pos) {
|
||||||
|
if let Some(b) = self.layout.find_boat_at_position(*pos) {
|
||||||
|
self.curr_boat = self.layout.0.iter().position(|s| s == b).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Handle continue mouse action
|
||||||
|
else if is_moving_boat {
|
||||||
|
if let Some(pos) = coordinates_mapper.get(&src_pos) {
|
||||||
|
self.layout.0[self.curr_boat].start = *pos;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let MouseEventKind::Up(_) = mouse.kind {
|
||||||
|
is_moving_boat = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if last_tick.elapsed() >= TICK_RATE {
|
||||||
|
last_tick = Instant::now();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let other_boats = ColoredCells {
|
fn ui<B: Backend>(&mut self, f: &mut Frame<B>) -> CoordinatesMapper {
|
||||||
color: Color::Gray,
|
let errors = self.layout.errors(self.rules);
|
||||||
cells: other_boats_cells,
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut coordinates_mapper = HashMap::new();
|
// Color of current boat
|
||||||
|
let current_boat = ColoredCells {
|
||||||
|
color: Color::Green,
|
||||||
|
cells: self.layout.0[self.curr_boat].all_coordinates(),
|
||||||
|
};
|
||||||
|
|
||||||
let mut legend = "n next boat \n\
|
// Color of invalid boats
|
||||||
|
let mut invalid_coordinates = vec![];
|
||||||
|
for (idx, pos) in self.layout.boats().iter().enumerate() {
|
||||||
|
if idx == self.curr_boat {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if !self
|
||||||
|
.layout
|
||||||
|
.check_present_boat_position(idx, self.rules)
|
||||||
|
.is_empty()
|
||||||
|
{
|
||||||
|
invalid_coordinates.append(&mut pos.all_coordinates());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let invalid_boats = ColoredCells {
|
||||||
|
color: Color::Red,
|
||||||
|
cells: invalid_coordinates,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Color of other boats
|
||||||
|
let mut other_boats_cells = vec![];
|
||||||
|
for boat in &self.layout.0 {
|
||||||
|
other_boats_cells.append(&mut boat.all_coordinates());
|
||||||
|
}
|
||||||
|
|
||||||
|
let other_boats = ColoredCells {
|
||||||
|
color: Color::Gray,
|
||||||
|
cells: other_boats_cells,
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut coordinates_mapper = HashMap::new();
|
||||||
|
|
||||||
|
let mut legend = "n next boat \n\
|
||||||
r rotate boat \n\n\
|
r rotate boat \n\n\
|
||||||
← ↓↑ → move boat \n\n"
|
← ↓↑ → move boat \n\n"
|
||||||
.to_string();
|
.to_string();
|
||||||
if errors.is_empty() {
|
if errors.is_empty() {
|
||||||
legend.push_str("Enter confirm layout");
|
legend.push_str("Enter confirm layout");
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut game_map_widget = GameMapWidget::new(rules)
|
let mut game_map_widget = GameMapWidget::new(self.rules)
|
||||||
.set_default_empty_char(' ')
|
.set_default_empty_char(' ')
|
||||||
.add_colored_cells(current_boat)
|
.add_colored_cells(current_boat)
|
||||||
.add_colored_cells(invalid_boats)
|
.add_colored_cells(invalid_boats)
|
||||||
.add_colored_cells(other_boats)
|
.add_colored_cells(other_boats)
|
||||||
.set_title("Choose your boat layout")
|
.set_title("Choose your boat layout")
|
||||||
.set_yield_func(|c, r| {
|
.set_yield_func(|c, r| {
|
||||||
for i in 0..r.width {
|
for i in 0..r.width {
|
||||||
for j in 0..r.height {
|
for j in 0..r.height {
|
||||||
coordinates_mapper.insert(Coordinates::new(r.x + i, r.y + j), c);
|
coordinates_mapper.insert(Coordinates::new(r.x + i, r.y + j), c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.set_legend(legend);
|
||||||
|
|
||||||
|
// Color of neighbors if boats can not touch
|
||||||
|
if !self.rules.boats_can_touch {
|
||||||
|
let mut boats_neighbors_cells = vec![];
|
||||||
|
for boat in &self.layout.0 {
|
||||||
|
for pos in boat.neighbor_coordinates(self.rules) {
|
||||||
|
boats_neighbors_cells.push(pos);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
|
||||||
.set_legend(legend);
|
|
||||||
|
|
||||||
// Color of neighbors if boats can not touch
|
game_map_widget = game_map_widget.add_colored_cells(ColoredCells {
|
||||||
if !rules.boats_can_touch {
|
color: Color::Rgb(30, 30, 30),
|
||||||
let mut boats_neighbors_cells = vec![];
|
cells: boats_neighbors_cells,
|
||||||
for boat in &model.layout.0 {
|
});
|
||||||
for pos in boat.neighbor_coordinates(rules) {
|
}
|
||||||
boats_neighbors_cells.push(pos);
|
|
||||||
|
let (w, h) = game_map_widget.estimated_size();
|
||||||
|
let area = centered_rect_size(w, h, &f.size());
|
||||||
|
f.render_widget(game_map_widget, area);
|
||||||
|
|
||||||
|
if !errors.is_empty() {
|
||||||
|
let messages = ["INVALID_LAYOUT", errors[0]];
|
||||||
|
for (i, msg) in messages.iter().enumerate() {
|
||||||
|
let paragraph = Paragraph::new(*msg).style(Style::default().fg(Color::Red));
|
||||||
|
f.render_widget(
|
||||||
|
paragraph,
|
||||||
|
centered_text(
|
||||||
|
msg,
|
||||||
|
&Rect::new(f.size().x, area.bottom() + i as u16, f.size().width, 1),
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
game_map_widget = game_map_widget.add_colored_cells(ColoredCells {
|
coordinates_mapper
|
||||||
color: Color::Rgb(30, 30, 30),
|
|
||||||
cells: boats_neighbors_cells,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let (w, h) = game_map_widget.estimated_size();
|
|
||||||
let area = centered_rect_size(w, h, &f.size());
|
|
||||||
f.render_widget(game_map_widget, area);
|
|
||||||
|
|
||||||
if !errors.is_empty() {
|
|
||||||
let messages = ["INVALID_LAYOUT", errors[0]];
|
|
||||||
for (i, msg) in messages.iter().enumerate() {
|
|
||||||
let paragraph = Paragraph::new(*msg).style(Style::default().fg(Color::Red));
|
|
||||||
f.render_widget(
|
|
||||||
paragraph,
|
|
||||||
centered_text(
|
|
||||||
msg,
|
|
||||||
&Rect::new(f.size().x, area.bottom() + i as u16, f.size().width, 1),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
coordinates_mapper
|
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user