Start to build cli player
This commit is contained in:
14
rust/cli_player/Cargo.toml
Normal file
14
rust/cli_player/Cargo.toml
Normal file
@ -0,0 +1,14 @@
|
||||
[package]
|
||||
name = "cli_player"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
sea_battle_backend = { path = "../sea_battle_backend" }
|
||||
clap = { version = "3.2.17", features = ["derive"] }
|
||||
log = "0.4.17"
|
||||
env_logger = "0.9.0"
|
||||
tui = "0.19.0"
|
||||
crossterm = "0.25.0"
|
14
rust/cli_player/src/cli_args.rs
Normal file
14
rust/cli_player/src/cli_args.rs
Normal file
@ -0,0 +1,14 @@
|
||||
use clap::Parser;
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
pub struct CliArgs {
|
||||
// TODO: switch default sever uri to real one when we get one
|
||||
/// Upstream server to use
|
||||
#[clap(
|
||||
short,
|
||||
long,
|
||||
value_parser,
|
||||
default_value = "https://fixme.communiquons.org"
|
||||
)]
|
||||
server_uri: String,
|
||||
}
|
2
rust/cli_player/src/lib.rs
Normal file
2
rust/cli_player/src/lib.rs
Normal file
@ -0,0 +1,2 @@
|
||||
pub mod cli_args;
|
||||
pub mod ui_screens;
|
47
rust/cli_player/src/main.rs
Normal file
47
rust/cli_player/src/main.rs
Normal file
@ -0,0 +1,47 @@
|
||||
use std::error::Error;
|
||||
use std::io;
|
||||
use std::time::Duration;
|
||||
|
||||
use cli_player::ui_screens::select_play_mode;
|
||||
use crossterm::event::DisableMouseCapture;
|
||||
use crossterm::event::EnableMouseCapture;
|
||||
use crossterm::execute;
|
||||
use crossterm::terminal::EnterAlternateScreen;
|
||||
use crossterm::terminal::LeaveAlternateScreen;
|
||||
use crossterm::terminal::{disable_raw_mode, enable_raw_mode};
|
||||
use env_logger::Env;
|
||||
use tui::backend::CrosstermBackend;
|
||||
use tui::Terminal;
|
||||
|
||||
pub fn main() -> Result<(), Box<dyn Error>> {
|
||||
env_logger::Builder::from_env(Env::default().default_filter_or("info")).init();
|
||||
|
||||
// setup terminal
|
||||
enable_raw_mode()?;
|
||||
let mut stdout = io::stdout();
|
||||
execute!(stdout, EnterAlternateScreen, EnableMouseCapture)?;
|
||||
let backend = CrosstermBackend::new(stdout);
|
||||
let mut terminal = Terminal::new(backend)?;
|
||||
|
||||
// create app and run it
|
||||
let tick_rate = Duration::from_millis(250);
|
||||
|
||||
let res = select_play_mode::select_play_mode(&mut terminal, tick_rate);
|
||||
|
||||
// restore terminal
|
||||
disable_raw_mode()?;
|
||||
execute!(
|
||||
terminal.backend_mut(),
|
||||
LeaveAlternateScreen,
|
||||
DisableMouseCapture
|
||||
)?;
|
||||
terminal.show_cursor()?;
|
||||
|
||||
if let Err(err) = &res {
|
||||
println!("{:?}", err)
|
||||
}
|
||||
|
||||
println!("selected play mode: {:?}", res.unwrap());
|
||||
|
||||
Ok(())
|
||||
}
|
2
rust/cli_player/src/ui_screens/mod.rs
Normal file
2
rust/cli_player/src/ui_screens/mod.rs
Normal file
@ -0,0 +1,2 @@
|
||||
pub mod select_play_mode;
|
||||
pub mod utils;
|
104
rust/cli_player/src/ui_screens/select_play_mode.rs
Normal file
104
rust/cli_player/src/ui_screens/select_play_mode.rs
Normal file
@ -0,0 +1,104 @@
|
||||
use std::io;
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use crate::ui_screens::utils::centered_rect;
|
||||
use crossterm::event;
|
||||
use crossterm::event::{Event, KeyCode};
|
||||
use tui::backend::Backend;
|
||||
use tui::style::{Color, Modifier, Style};
|
||||
use tui::text::Text;
|
||||
use tui::widgets::{Block, Borders, List, ListItem, ListState};
|
||||
use tui::{Frame, Terminal};
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Copy, Clone, Default)]
|
||||
pub enum SelectPlayModeResult {
|
||||
#[default]
|
||||
PlayAgainstBot,
|
||||
PlayRandom,
|
||||
Exit,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct PlayModeDescription {
|
||||
name: &'static str,
|
||||
value: SelectPlayModeResult,
|
||||
}
|
||||
|
||||
const AVAILABLE_PLAY_MODES: [PlayModeDescription; 3] = [
|
||||
PlayModeDescription {
|
||||
name: "Play against bot (offline)",
|
||||
value: SelectPlayModeResult::PlayAgainstBot,
|
||||
},
|
||||
PlayModeDescription {
|
||||
name: "Play against random player (online)",
|
||||
value: SelectPlayModeResult::PlayRandom,
|
||||
},
|
||||
PlayModeDescription {
|
||||
name: "Exit app",
|
||||
value: SelectPlayModeResult::Exit,
|
||||
},
|
||||
];
|
||||
|
||||
#[derive(Default)]
|
||||
struct SelectPlayModeScreen {
|
||||
state: ListState,
|
||||
curr_selection: usize,
|
||||
}
|
||||
|
||||
pub fn select_play_mode<B: Backend>(
|
||||
terminal: &mut Terminal<B>,
|
||||
tick_rate: Duration,
|
||||
) -> io::Result<SelectPlayModeResult> {
|
||||
let mut model = SelectPlayModeScreen::default();
|
||||
|
||||
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 crossterm::event::poll(timeout)? {
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn ui<B: Backend>(f: &mut Frame<B>, model: &mut SelectPlayModeScreen) {
|
||||
let area = centered_rect(60, 20, 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(Color::Green)
|
||||
.add_modifier(Modifier::BOLD),
|
||||
)
|
||||
.highlight_symbol(">> ");
|
||||
|
||||
f.render_stateful_widget(items, area, &mut model.state);
|
||||
}
|
28
rust/cli_player/src/ui_screens/utils.rs
Normal file
28
rust/cli_player/src/ui_screens/utils.rs
Normal file
@ -0,0 +1,28 @@
|
||||
use tui::layout::{Constraint, Direction, Layout, Rect};
|
||||
|
||||
/// helper function to create a centered rect using up certain percentage of the available rect `r`
|
||||
pub fn centered_rect(percent_x: u16, percent_y: u16, r: Rect) -> Rect {
|
||||
let popup_layout = Layout::default()
|
||||
.direction(Direction::Vertical)
|
||||
.constraints(
|
||||
[
|
||||
Constraint::Percentage((100 - percent_y) / 2),
|
||||
Constraint::Percentage(percent_y),
|
||||
Constraint::Percentage((100 - percent_y) / 2),
|
||||
]
|
||||
.as_ref(),
|
||||
)
|
||||
.split(r);
|
||||
|
||||
Layout::default()
|
||||
.direction(Direction::Horizontal)
|
||||
.constraints(
|
||||
[
|
||||
Constraint::Percentage((100 - percent_x) / 2),
|
||||
Constraint::Percentage(percent_x),
|
||||
Constraint::Percentage((100 - percent_x) / 2),
|
||||
]
|
||||
.as_ref(),
|
||||
)
|
||||
.split(popup_layout[1])[1]
|
||||
}
|
Reference in New Issue
Block a user