Timer
This commit is contained in:
+46
-27
@@ -26,6 +26,7 @@ use ratatui::widgets::{Block, Paragraph, Wrap};
|
||||
use render::{BoardWidget, logline_to_line};
|
||||
use std::io;
|
||||
use std::process::ExitCode;
|
||||
use std::time::{Duration, Instant};
|
||||
use ratatui::symbols::merge::MergeStrategy;
|
||||
use term::TerminalCaps;
|
||||
|
||||
@@ -107,34 +108,43 @@ fn main() -> ExitCode {
|
||||
ExitCode::SUCCESS
|
||||
}
|
||||
|
||||
/// Draw-then-handle-input loop. Returns once the user asks to quit.
|
||||
/// The target frame interval: 30 frames per second.
|
||||
const FRAME: Duration = Duration::from_nanos(1_000_000_000 / 30);
|
||||
|
||||
/// Real-time draw / input / update loop at ~30 FPS. Returns once the user quits.
|
||||
///
|
||||
/// Unlike a turn-based loop, this never blocks indefinitely: it waits for input
|
||||
/// only until the next frame deadline (`event::poll`), then advances the game by
|
||||
/// the real time elapsed since the last tick. `poll` returns the instant a key
|
||||
/// arrives, so input stays responsive while the game keeps ticking on its own.
|
||||
fn run(
|
||||
terminal: &mut ratatui::DefaultTerminal,
|
||||
game: &mut GameState,
|
||||
ui: &mut Ui,
|
||||
) -> std::io::Result<()> {
|
||||
let mut last_tick = Instant::now();
|
||||
loop {
|
||||
// Redraw every iteration; ratatui diffs against the previous frame so
|
||||
// only changed cells are actually written to the terminal.
|
||||
terminal.draw(|frame| draw(frame, game, ui))?;
|
||||
|
||||
match event::read()? {
|
||||
Event::Key(key) => {
|
||||
// Wait for input only up to the next frame deadline so the loop wakes to
|
||||
// tick even with no keypresses.
|
||||
let timeout = FRAME.saturating_sub(last_tick.elapsed());
|
||||
if event::poll(timeout)? {
|
||||
match event::read()? {
|
||||
// Act on Press and Repeat so holding a key moves the player
|
||||
// continuously (the Kitty protocol emits Repeat events while a
|
||||
// key is held). Ignore Release, which that protocol also emits
|
||||
// and which would otherwise fire a second, spurious move.
|
||||
if key.kind == KeyEventKind::Release {
|
||||
continue;
|
||||
}
|
||||
match key.code {
|
||||
// continuously (the Kitty protocol emits Repeat events while a key
|
||||
// is held). Ignore Release, which that protocol also emits and
|
||||
// which would otherwise fire a second, spurious move per keypress.
|
||||
Event::Key(key) if key.kind != KeyEventKind::Release => match key.code {
|
||||
KeyCode::Char('q') | KeyCode::Esc => return Ok(()),
|
||||
KeyCode::Up => game.try_move(0, -1),
|
||||
KeyCode::Down => game.try_move(0, 1),
|
||||
KeyCode::Left => game.try_move(-1, 0),
|
||||
// Right arrow moves right; 'l' is reserved for the log panel.
|
||||
// Right arrow moves right; 'l' is the log panel.
|
||||
KeyCode::Right => game.try_move(1, 0),
|
||||
// 'l' toggles the log panel; reset the scroll to the newest.
|
||||
// 'l' toggles the log panel; reset scroll to newest.
|
||||
KeyCode::Char('l') => {
|
||||
ui.log_open = !ui.log_open;
|
||||
ui.scroll = 0;
|
||||
@@ -143,22 +153,31 @@ fn run(
|
||||
KeyCode::PageUp if ui.log_open => scroll_log(ui, game, -5),
|
||||
KeyCode::PageDown if ui.log_open => scroll_log(ui, game, 5),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
// Wheel scrolls the open log; dragging the divider resizes it.
|
||||
Event::Mouse(m) if ui.log_open => match m.kind {
|
||||
MouseEventKind::ScrollUp => scroll_log(ui, game, -1),
|
||||
MouseEventKind::ScrollDown => scroll_log(ui, game, 1),
|
||||
MouseEventKind::Drag(_) => {
|
||||
// The divider sits at row `total - log_height`; dragging it up
|
||||
// (smaller row) grows the panel. Keep both regions usable.
|
||||
let total = terminal.size()?.height;
|
||||
let target = total.saturating_sub(m.row);
|
||||
ui.log_height = target.clamp(3, total.saturating_sub(3).max(3));
|
||||
}
|
||||
},
|
||||
// Wheel scrolls the open log; dragging the divider resizes it.
|
||||
Event::Mouse(m) if ui.log_open => match m.kind {
|
||||
MouseEventKind::ScrollUp => scroll_log(ui, game, -1),
|
||||
MouseEventKind::ScrollDown => scroll_log(ui, game, 1),
|
||||
MouseEventKind::Drag(_) => {
|
||||
// The divider sits at row `total - log_height`; dragging
|
||||
// it up (smaller row) grows the panel. Keep both usable.
|
||||
let total = terminal.size()?.height;
|
||||
let target = total.saturating_sub(m.row);
|
||||
ui.log_height = target.clamp(3, total.saturating_sub(3).max(3));
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
_ => {}
|
||||
},
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
// Advance the game by the real time elapsed since the last tick. Only
|
||||
// resetting `last_tick` when we actually tick means time spent waiting on
|
||||
// input (or skipped Release events) is preserved in the next `dt`.
|
||||
if last_tick.elapsed() >= FRAME {
|
||||
let dt = last_tick.elapsed();
|
||||
last_tick = Instant::now();
|
||||
game.tick(dt);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user