diff --git a/src/game.rs b/src/game.rs index 41534f3..853768a 100644 --- a/src/game.rs +++ b/src/game.rs @@ -206,44 +206,50 @@ impl Game { } pub fn handle_input(&mut self, vw: usize, vh: usize) { - let one_shot = self.input.update(); - match one_shot { - Action::Quit => { - self.running = false; - } - Action::Paint(brush) => { - let mat = brush.to_material(); - let cx = self.cam_x + (vw as i32 / 2); - let cy = self.cam_y + (vh as i32 / 2); - let r = 2; - for dy in -r..=r { - for dx in -r..=r { - if dx * dx + dy * dy <= r * r + 1 { - if let Some(m) = mat { - self.grid.set_material(cx + dx, cy + dy, m); - } else { - self.grid.set(cx + dx, cy + dy, crate::world::cell::Cell::empty()); + let one_shots = self.input.update(); + + for action in one_shots { + match action { + Action::Quit => { + self.running = false; + return; + } + Action::Paint(brush) => { + let mat = brush.to_material(); + let cx = self.cam_x + (vw as i32 / 2); + let cy = self.cam_y + (vh as i32 / 2); + let r = 2; + for dy in -r..=r { + for dx in -r..=r { + if dx * dx + dy * dy <= r * r + 1 { + if let Some(m) = mat { + self.grid.set_material(cx + dx, cy + dy, m); + } else { + self.grid.set(cx + dx, cy + dy, crate::world::cell::Cell::empty()); + } } } } } + _ => {} } - _ => {} } if !self.running { return; } + // Jump: only on press, not held + if self.input.jump_requested() { + let on_ground = self.check_on_ground(); + self.player.jump(&mut self.entities, on_ground); + } + + // Movement: applied every tick while held for action in self.input.held_actions() { match action { - Action::Quit => self.running = false, Action::MoveLeft => self.player.move_left(&mut self.entities), Action::MoveRight => self.player.move_right(&mut self.entities), - Action::Jump => { - let on_ground = self.check_on_ground(); - self.player.jump(&mut self.entities, on_ground); - } Action::MoveCameraLeft => self.cam_x -= 2, Action::MoveCameraRight => self.cam_x += 2, Action::MoveCameraUp => self.cam_y -= 2, diff --git a/src/input.rs b/src/input.rs index 6ee902b..5b16df3 100644 --- a/src/input.rs +++ b/src/input.rs @@ -1,5 +1,5 @@ use crossterm::event::{self, Event, KeyCode, KeyEvent, KeyEventKind, KeyModifiers}; -use std::time::Duration; +use std::time::{Duration, Instant}; #[derive(Clone, Copy, PartialEq, Eq, Debug)] pub enum Action { @@ -30,27 +30,35 @@ pub enum MaterialBrush { Erase, } -#[derive(Clone, Copy, PartialEq, Eq)] +#[derive(Clone, Copy, PartialEq, Eq, Hash)] pub enum HeldKey { Left, Right, - Jump, CamLeft, CamRight, CamUp, CamDown, } +struct HeldState { + last_seen: Instant, + just_pressed: bool, +} + +const HOLD_TIMEOUT: Duration = Duration::from_millis(80); + pub struct InputHandler { pub paint_brush: MaterialBrush, - held: Vec, + held: std::collections::HashMap, + jump_pressed: bool, } impl InputHandler { pub fn new() -> Self { Self { paint_brush: MaterialBrush::Sand, - held: Vec::new(), + held: std::collections::HashMap::new(), + jump_pressed: false, } } @@ -58,7 +66,6 @@ impl InputHandler { match code { KeyCode::Left | KeyCode::Char('a') => Some(HeldKey::Left), KeyCode::Right | KeyCode::Char('d') => Some(HeldKey::Right), - KeyCode::Up | KeyCode::Char('w') | KeyCode::Char(' ') => Some(HeldKey::Jump), KeyCode::Char('h') => Some(HeldKey::CamLeft), KeyCode::Char('l') => Some(HeldKey::CamRight), KeyCode::Char('k') => Some(HeldKey::CamUp), @@ -67,24 +74,32 @@ impl InputHandler { } } - fn hold(&mut self, key: HeldKey) { - if !self.held.contains(&key) { - self.held.push(key); - } + fn mark_held(&mut self, key: HeldKey) { + let state = self.held.entry(key).or_insert(HeldState { last_seen: Instant::now(), just_pressed: false }); + state.last_seen = Instant::now(); + } + + fn mark_pressed(&mut self, key: HeldKey) { + self.held.insert(key, HeldState { last_seen: Instant::now(), just_pressed: true }); } fn release(&mut self, key: HeldKey) { - self.held.retain(|&k| k != key); + self.held.remove(&key); } pub fn is_held(&self, key: HeldKey) -> bool { - self.held.contains(&key) + self.held.contains_key(&key) } - /// Drain all pending input events, update held key state, - /// and return one-shot actions (quit, paint, etc). - pub fn update(&mut self) -> Action { - let mut one_shot = Action::None; + /// Drain all pending input events, update held key state. + /// Returns one-shot actions (quit, paint, jump-on-press). + pub fn update(&mut self) -> Vec { + let mut one_shots = Vec::new(); + self.jump_pressed = false; + let now = Instant::now(); + + // Expire stale held keys (no Repeat received within timeout) + self.held.retain(|_, state| now.duration_since(state.last_seen) < HOLD_TIMEOUT); while event::poll(Duration::from_millis(0)).unwrap_or(false) { let ev = match event::read() { @@ -93,77 +108,109 @@ impl InputHandler { }; if let Event::Key(KeyEvent { code, modifiers, kind, .. }) = ev { - let is_press = kind == KeyEventKind::Press || kind == KeyEventKind::Repeat; + let is_press = kind == KeyEventKind::Press; + let is_repeat = kind == KeyEventKind::Repeat; let is_release = kind == KeyEventKind::Release; if modifiers.contains(KeyModifiers::CONTROL) && code == KeyCode::Char('c') { - return Action::Quit; - } - - if let Some(held_key) = Self::key_to_held(code) { - if is_press { - self.hold(held_key); - } else if is_release { - self.release(held_key); - } + one_shots.push(Action::Quit); continue; } + // Jump: only on initial press, not repeat if is_press { match code { - KeyCode::Char('q') => return Action::Quit, - KeyCode::Char('1') => { self.paint_brush = MaterialBrush::Sand; one_shot = Action::Paint(MaterialBrush::Sand); } - KeyCode::Char('2') => { self.paint_brush = MaterialBrush::Water; one_shot = Action::Paint(MaterialBrush::Water); } - KeyCode::Char('3') => { self.paint_brush = MaterialBrush::Stone; one_shot = Action::Paint(MaterialBrush::Stone); } - KeyCode::Char('4') => { self.paint_brush = MaterialBrush::Lava; one_shot = Action::Paint(MaterialBrush::Lava); } - KeyCode::Char('5') => { self.paint_brush = MaterialBrush::Wood; one_shot = Action::Paint(MaterialBrush::Wood); } - KeyCode::Char('6') => { self.paint_brush = MaterialBrush::Acid; one_shot = Action::Paint(MaterialBrush::Acid); } - KeyCode::Char('7') => { self.paint_brush = MaterialBrush::Grass; one_shot = Action::Paint(MaterialBrush::Grass); } - KeyCode::Char('8') => { self.paint_brush = MaterialBrush::Dirt; one_shot = Action::Paint(MaterialBrush::Dirt); } - KeyCode::Char('9') => { self.paint_brush = MaterialBrush::Fire; one_shot = Action::Paint(MaterialBrush::Fire); } - KeyCode::Char('0') => { self.paint_brush = MaterialBrush::Flesh; one_shot = Action::Paint(MaterialBrush::Flesh); } - KeyCode::Char('x') => { self.paint_brush = MaterialBrush::Erase; one_shot = Action::Paint(MaterialBrush::Erase); } + KeyCode::Up | KeyCode::Char('w') | KeyCode::Char(' ') => { + self.jump_pressed = true; + continue; + } + KeyCode::Char('q') => { + one_shots.push(Action::Quit); + continue; + } + KeyCode::Char('1') => { self.paint_brush = MaterialBrush::Sand; one_shots.push(Action::Paint(MaterialBrush::Sand)); continue; } + KeyCode::Char('2') => { self.paint_brush = MaterialBrush::Water; one_shots.push(Action::Paint(MaterialBrush::Water)); continue; } + KeyCode::Char('3') => { self.paint_brush = MaterialBrush::Stone; one_shots.push(Action::Paint(MaterialBrush::Stone)); continue; } + KeyCode::Char('4') => { self.paint_brush = MaterialBrush::Lava; one_shots.push(Action::Paint(MaterialBrush::Lava)); continue; } + KeyCode::Char('5') => { self.paint_brush = MaterialBrush::Wood; one_shots.push(Action::Paint(MaterialBrush::Wood)); continue; } + KeyCode::Char('6') => { self.paint_brush = MaterialBrush::Acid; one_shots.push(Action::Paint(MaterialBrush::Acid)); continue; } + KeyCode::Char('7') => { self.paint_brush = MaterialBrush::Grass; one_shots.push(Action::Paint(MaterialBrush::Grass)); continue; } + KeyCode::Char('8') => { self.paint_brush = MaterialBrush::Dirt; one_shots.push(Action::Paint(MaterialBrush::Dirt)); continue; } + KeyCode::Char('9') => { self.paint_brush = MaterialBrush::Fire; one_shots.push(Action::Paint(MaterialBrush::Fire)); continue; } + KeyCode::Char('0') => { self.paint_brush = MaterialBrush::Flesh; one_shots.push(Action::Paint(MaterialBrush::Flesh)); continue; } + KeyCode::Char('x') => { self.paint_brush = MaterialBrush::Erase; one_shots.push(Action::Paint(MaterialBrush::Erase)); continue; } _ => {} } } + + // Movement keys: track held state + if let Some(held_key) = Self::key_to_held(code) { + if is_press { + self.mark_pressed(held_key); + } else if is_repeat { + self.mark_held(held_key); + } else if is_release { + self.release(held_key); + } + } } } - one_shot + // Clear just_pressed flags + for state in self.held.values_mut() { + state.just_pressed = false; + } + + one_shots } pub fn held_actions(&self) -> Vec { let mut actions = Vec::new(); - if self.is_held(HeldKey::Left) { + + // Left/Right: last pressed wins if both held + let left = self.is_held(HeldKey::Left); + let right = self.is_held(HeldKey::Right); + + if left && !right { actions.push(Action::MoveLeft); - } - if self.is_held(HeldKey::Right) { + } else if right && !left { actions.push(Action::MoveRight); + } else if left && right { + // Both held — check which was pressed more recently + let left_time = self.held.get(&HeldKey::Left).map(|s| s.last_seen); + let right_time = self.held.get(&HeldKey::Right).map(|s| s.last_seen); + match (left_time, right_time) { + (Some(lt), Some(rt)) => { + if lt > rt { + actions.push(Action::MoveLeft); + } else { + actions.push(Action::MoveRight); + } + } + _ => {} + } } - if self.is_held(HeldKey::Jump) { - actions.push(Action::Jump); - } - if self.is_held(HeldKey::CamLeft) { - actions.push(Action::MoveCameraLeft); - } - if self.is_held(HeldKey::CamRight) { - actions.push(Action::MoveCameraRight); - } - if self.is_held(HeldKey::CamUp) { - actions.push(Action::MoveCameraUp); - } - if self.is_held(HeldKey::CamDown) { - actions.push(Action::MoveCameraDown); - } + + if self.is_held(HeldKey::CamLeft) { actions.push(Action::MoveCameraLeft); } + if self.is_held(HeldKey::CamRight) { actions.push(Action::MoveCameraRight); } + if self.is_held(HeldKey::CamUp) { actions.push(Action::MoveCameraUp); } + if self.is_held(HeldKey::CamDown) { actions.push(Action::MoveCameraDown); } + actions } + pub fn jump_requested(&self) -> bool { + self.jump_pressed + } + pub fn release_all(&mut self) { self.held.clear(); + self.jump_pressed = false; } pub fn poll(&mut self) -> Action { - self.update() + let actions = self.update(); + actions.into_iter().next().unwrap_or(Action::None) } }