This repository has been archived on 2025-03-28. You can view files and clone it, but cannot push or open issues or pull requests.
SeaBattle/rust/cli_player/src/ui_screens/configure_game_rules.rs

226 lines
7.5 KiB
Rust

extern crate num as num_renamed;
use std::io;
use std::time::{Duration, Instant};
use crossterm::event;
use crossterm::event::{Event, KeyCode};
use tui::backend::Backend;
use tui::layout::{Constraint, Direction, Layout, Margin};
use tui::style::{Color, Style};
use tui::widgets::*;
use tui::{Frame, Terminal};
use sea_battle_backend::data::GameRules;
use crate::constants::TICK_RATE;
use crate::ui_screens::utils::centered_rect_size;
use crate::ui_screens::ScreenResult;
use crate::ui_widgets::button_widget::ButtonWidget;
use crate::ui_widgets::checkbox_widget::CheckboxWidget;
use crate::ui_widgets::text_editor_widget::TextEditorWidget;
#[derive(num_derive::FromPrimitive, num_derive::ToPrimitive, Eq, PartialEq)]
enum EditingField {
MapWidth = 0,
MapHeight,
BoatsList,
BoatsCanTouch,
PlayerContinueOnHit,
Cancel,
OK,
}
struct GameRulesConfigurationScreen {
rules: GameRules,
curr_field: EditingField,
}
pub fn configure_play_rules<B: Backend>(
rules: GameRules,
terminal: &mut Terminal<B>,
) -> io::Result<ScreenResult<GameRules>> {
let mut model = GameRulesConfigurationScreen {
rules,
curr_field: EditingField::OK,
};
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 crossterm::event::poll(timeout)? {
let mut cursor_pos = model.curr_field as i32;
if let Event::Key(key) = event::read()? {
match key.code {
// Quit app
KeyCode::Char('q') => return Ok(ScreenResult::Canceled),
// Navigate between fields
KeyCode::Up | KeyCode::Left => cursor_pos -= 1,
KeyCode::Down | KeyCode::Right | KeyCode::Tab => cursor_pos += 1,
// Submit results
KeyCode::Enter => {
if model.curr_field == EditingField::Cancel {
return Ok(ScreenResult::Canceled);
}
if model.curr_field == EditingField::OK && model.rules.is_valid() {
return Ok(ScreenResult::Ok(model.rules));
}
}
KeyCode::Char(' ') => {
if model.curr_field == EditingField::BoatsCanTouch {
model.rules.boats_can_touch = !model.rules.boats_can_touch;
}
if model.curr_field == EditingField::PlayerContinueOnHit {
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);
}
}
_ => {}
}
}
// Apply new cursor position
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>(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);
}
}