use std::io;
use std::net::SocketAddr;
use std::time::Duration;
use clap::Parser;
use crossterm::event::{self, Event};
use flatland_client_lib::{connect, default_server_addr, ConnectOptions, GameClient};
use flatland_tui::{draw, InputAction, MovementState};
use ratatui::backend::CrosstermBackend;
use ratatui::Terminal;
use tokio::sync::mpsc;
use tracing::info;
#[derive(Debug, Parser)]
#[command(about = "Ratatui play client — connect to flatland-server")]
pub struct PlayArgs {
#[arg(short, long, default_value = "traveler")]
pub name: String,
#[arg(long, default_value_t = default_server_addr())]
pub server: SocketAddr,
}
pub async fn run_with_options(opts: ConnectOptions) -> anyhow::Result<()> {
let session = connect(opts).await?;
let mut client = GameClient::new(session);
client.wait_until_ready().await?;
info!(
entity_id = client.entity_id(),
"connected — use WASD to move, q to quit"
);
let (key_tx, mut key_rx) = mpsc::unbounded_channel::<Event>();
std::thread::spawn(move || {
loop {
if event::poll(Duration::from_millis(50)).unwrap_or(false) {
if let Ok(ev) = event::read() {
if key_tx.send(ev).is_err() {
break;
}
}
}
}
});
crate::terminal::enter()?;
let backend = CrosstermBackend::new(io::stdout());
let mut ratatui = Terminal::new(backend)?;
let mut movement_state = MovementState::default();
let mut tick_interval = tokio::time::interval(Duration::from_millis(33));
let mut intent_interval = tokio::time::interval(Duration::from_millis(100));
intent_interval.tick().await;
loop {
let movement = movement_state.current();
let movement_label = movement.label();
ratatui.draw(|frame| draw(frame, &client.state, movement_label))?;
tokio::select! {
_ = tick_interval.tick() => {
client.drain_events();
}
_ = intent_interval.tick() => {
let (forward, strafe) = movement.components();
if forward.abs() > f32::EPSILON || strafe.abs() > f32::EPSILON {
client.move_by(forward, strafe).await?;
} else {
client.stop().await?;
}
}
maybe_key = key_rx.recv() => {
if let Some(Event::Key(key)) = maybe_key {
match movement_state.apply_key(key) {
InputAction::Quit => break,
InputAction::None => {}
}
}
}
}
}
client.disconnect();
ratatui.show_cursor()?;
crate::terminal::leave()?;
Ok(())
}