Replace settings module with skirmish mode

Removed the settings UI and its keybinding logic, added a skirmish view
and corresponding keybindings, simplified the SettingsState to hold
skirmish configuration, updated module exports, and changed the CLI
default map height from 25 to 21. Also fixed the main menu selection
limit.
This commit is contained in:
2026-03-17 14:28:46 +01:00
parent aaa2c90426
commit 0577697059
14 changed files with 149 additions and 355 deletions
+2 -2
View File
@@ -1,6 +1,6 @@
use crate::app::{
App,
keybindings::{default_keybindings, main_menu_keybindings, settings_keybindings},
keybindings::{default_keybindings, main_menu_keybindings, skirmish_keybindings},
view::View,
};
use ratatui::crossterm::event::KeyEvent;
@@ -8,7 +8,7 @@ use ratatui::crossterm::event::KeyEvent;
pub fn handle_keybindings(app: &mut App, key_event: KeyEvent) {
match app.view {
View::MainMenu => main_menu_keybindings(app, &key_event),
View::Settings => settings_keybindings(app, &key_event),
View::Skirmish => skirmish_keybindings(app, &key_event),
_ => default_keybindings(app, &key_event),
}
}
+1 -3
View File
@@ -15,7 +15,7 @@ pub fn main_menu_keybindings(app: &mut App, event: &KeyEvent) {
}
Action::Down => {
app.states.main_menu.selected_view =
app.states.main_menu.selected_view.saturating_add(1).min(4);
app.states.main_menu.selected_view.saturating_add(1).min(3);
}
Action::Space => {
let selected_view: usize = app.states.main_menu.selected_view;
@@ -26,8 +26,6 @@ pub fn main_menu_keybindings(app: &mut App, event: &KeyEvent) {
app.view = View::PerkDecks;
} else if selected_view == 3 {
app.view = View::SkillsConfig;
} else if selected_view == 4 {
app.view = View::Settings;
}
}
_ => (),
+2 -2
View File
@@ -1,9 +1,9 @@
pub mod default;
pub mod keybindings;
pub mod main_menu;
pub mod settings;
pub mod skirmish;
pub use default::default_keybindings;
pub use keybindings::{Action, Group, KEYBINDINGS, KeyBinding, binding_for, event_to_action};
pub use main_menu::main_menu_keybindings;
pub use settings::settings_keybindings;
pub use skirmish::skirmish_keybindings;
-105
View File
@@ -1,105 +0,0 @@
use crate::app::{
App, View,
keybindings::{Action, event_to_action},
states::SettingsValue,
};
use ratatui::crossterm::event::KeyEvent;
use std::any::type_name_of_val;
pub fn settings_keybindings(app: &mut App, key_event: &KeyEvent) {
if let Some(action) = event_to_action(&key_event) {
match action {
Action::Up => {
app.states.settings.selected_setting = app
.states
.settings
.selected_setting
.saturating_sub(1)
.max(0)
}
Action::Down => {
app.states.settings.selected_setting = app
.states
.settings
.selected_setting
.saturating_add(1)
.min(9)
}
Action::Esc => app.view = View::MainMenu,
Action::Space => app.states.settings.show_popup = !app.states.settings.show_popup,
Action::Enter => {
// FIXME: No feedback
// FIXME: Extract match block into a function
if app.states.settings.show_popup {
let option =
&app.states.settings.options[app.states.settings.selected_setting].value;
let option_type: &'static str = type_name_of_val(option);
let new_value: String = app.states.settings.selected_setting_new_value.clone();
match option_type {
"u8" => {
if let Ok(value) = new_value.parse::<u8>() {
app.states.settings.options[app.states.settings.selected_setting]
.value = SettingsValue::U8(value);
app.states.settings.selected_setting_new_value = "".to_string();
app.states.settings.error_message = "".to_string();
app.states.settings.show_popup = false;
} else {
app.states.settings.error_message = "Invalid value".to_string();
}
}
"f32" => {
if let Ok(value) = new_value.parse::<f32>() {
app.states.settings.options[app.states.settings.selected_setting]
.value = SettingsValue::F32(value);
app.states.settings.selected_setting_new_value = "".to_string();
app.states.settings.error_message = "".to_string();
app.states.settings.show_popup = false;
} else {
app.states.settings.error_message = "Invalid value".to_string();
}
}
"u16" => {
if let Ok(value) = new_value.parse::<u16>() {
app.states.settings.options[app.states.settings.selected_setting]
.value = SettingsValue::U16(value);
app.states.settings.selected_setting_new_value = "".to_string();
app.states.settings.error_message = "".to_string();
app.states.settings.show_popup = false;
} else {
app.states.settings.error_message = "Invalid value".to_string();
}
}
"std::string::String" => {
app.states.settings.options[app.states.settings.selected_setting]
.value = SettingsValue::Text(new_value);
app.states.settings.selected_setting_new_value = "".to_string();
app.states.settings.error_message = "".to_string();
app.states.settings.show_popup = false;
}
_ => (),
}
}
}
Action::Backspace => {
if app.states.settings.show_popup {
app.states.settings.selected_setting_new_value.pop();
}
}
Action::WildCard(c) => {
if app.states.settings.show_popup {
app.states.settings.selected_setting_new_value.push(c);
}
}
Action::Quit => app.exit = true,
Action::Quit2 => app.exit = true,
// _ => (),
}
}
}
+16
View File
@@ -0,0 +1,16 @@
use crate::app::{
App, View,
keybindings::{Action, event_to_action},
};
use ratatui::crossterm::event::KeyEvent;
pub fn skirmish_keybindings(app: &mut App, key_event: &KeyEvent) {
if let Some(action) = event_to_action(&key_event) {
match action {
Action::Quit => app.exit = true,
Action::Quit2 => app.exit = true,
Action::Esc => app.view = View::MainMenu,
_ => (),
}
}
}
+11 -52
View File
@@ -1,8 +1,5 @@
use crate::{
app::states::{
MainMenuState, PerkDecksState, SettingsOption, SettingsState, SettingsValue,
SkillsConfigState, SkirmishState,
},
app::states::{MainMenuState, PerkDecksState, SettingsState, SkillsConfigState, SkirmishState},
cli::Cli,
};
@@ -38,54 +35,16 @@ impl GameStates {
selected_skill: 0,
},
settings: SettingsState {
id: 4,
name: "Settings",
selected_setting: 0,
show_popup: false,
selected_setting_new_value: String::new(),
error_message: String::new(),
options: vec![
SettingsOption {
name: "Username",
value: SettingsValue::Text(args.username),
},
SettingsOption {
name: "Game Mode",
value: SettingsValue::GameMode(args.game_mode),
},
SettingsOption {
name: "Map Width",
value: SettingsValue::U8(args.map_width),
},
SettingsOption {
name: "Map Height",
value: SettingsValue::U8(args.map_height),
},
SettingsOption {
name: "Perk Deck",
value: SettingsValue::PerkDeck(args.perk_deck),
},
SettingsOption {
name: "Starting Wood",
value: SettingsValue::U8(args.starting_wood),
},
SettingsOption {
name: "Starting Iron",
value: SettingsValue::U8(args.starting_iron),
},
SettingsOption {
name: "Supply Limit",
value: SettingsValue::U8(args.supply_limit),
},
SettingsOption {
name: "XP Modifier",
value: SettingsValue::F32(args.xp_modifier),
},
SettingsOption {
name: "Skill Points Limit",
value: SettingsValue::U16(args.skill_points_limit),
},
],
username: args.username,
game_mode: args.game_mode,
map_width: args.map_width,
map_height: args.map_height,
perk_deck: args.perk_deck,
starting_wood: args.starting_wood,
starting_iron: args.starting_iron,
supply_limit: args.supply_limit,
xp_modifier: args.xp_modifier,
skill_points_limit: args.skill_points_limit,
},
}
}
+2 -2
View File
@@ -6,6 +6,6 @@ pub mod skirmish;
pub use main_menu::MainMenuState;
pub use perk_decks::{PerkDecks, PerkDecksState};
pub use settings::{GameMode, SettingsOption, SettingsState, SettingsValue};
pub use settings::SettingsState;
pub use skills_config::SkillsConfigState;
pub use skirmish::SkirmishState;
pub use skirmish::{GameMode, SkirmishState};
+11 -54
View File
@@ -1,58 +1,15 @@
use crate::app::states::PerkDecks;
use clap::ValueEnum;
use std::fmt::Display;
use crate::app::states::{GameMode, PerkDecks};
#[derive(Debug, Clone, PartialEq)]
pub struct SettingsState {
pub id: usize,
pub name: &'static str,
pub selected_setting: usize,
pub show_popup: bool,
pub selected_setting_new_value: String,
pub error_message: String,
pub options: Vec<SettingsOption>,
}
#[derive(Debug, Clone, PartialEq)]
pub struct SettingsOption {
pub name: &'static str,
pub value: SettingsValue,
}
#[derive(Debug, Clone, PartialEq)]
pub enum SettingsValue {
U8(u8),
F32(f32),
U16(u16),
Text(String),
GameMode(GameMode),
PerkDeck(PerkDecks),
}
impl Display for SettingsValue {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
SettingsValue::U8(v) => write!(f, "{}", v),
SettingsValue::F32(v) => write!(f, "{}", v),
SettingsValue::U16(v) => write!(f, "{}", v),
SettingsValue::Text(v) => write!(f, "{}", v),
SettingsValue::GameMode(v) => write!(f, "{}", v),
SettingsValue::PerkDeck(v) => write!(f, "{}", v),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, ValueEnum)]
pub enum GameMode {
LastManStanding,
FrontLines,
}
impl Display for GameMode {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
GameMode::FrontLines => write!(f, "Front Lines"),
GameMode::LastManStanding => write!(f, "Last Man Standing"),
}
}
pub username: String,
pub game_mode: GameMode,
pub map_width: u8,
pub map_height: u8,
pub perk_deck: PerkDecks,
pub starting_wood: u16,
pub starting_iron: u16,
pub supply_limit: u8,
pub xp_modifier: f32,
pub skill_points_limit: u16,
}
+8
View File
@@ -1,5 +1,13 @@
use clap::ValueEnum;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct SkirmishState {
pub id: usize,
pub name: &'static str,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, ValueEnum)]
pub enum GameMode {
LastManStanding,
FrontLines,
}
+2 -3
View File
@@ -1,6 +1,6 @@
use crate::app::{
App,
views::{default_view, main_menu_view, settings_view},
views::{default_view, main_menu_view, skirmish_view},
};
use clap::ValueEnum;
use ratatui::{buffer::Buffer, layout::Rect, widgets::Widget};
@@ -11,7 +11,6 @@ pub enum View {
Skirmish,
PerkDecks,
SkillsConfig,
Settings,
}
impl Widget for &App {
@@ -21,7 +20,7 @@ impl Widget for &App {
{
match self.view {
View::MainMenu => main_menu_view(self, area, buf),
View::Settings => settings_view(self, area, buf),
View::Skirmish => skirmish_view(self, area, buf),
_ => default_view(area, buf),
}
}
+2 -2
View File
@@ -1,7 +1,7 @@
pub mod default;
pub mod main_menu;
pub mod settings;
pub mod skirmish;
pub use default::default_view;
pub use main_menu::main_menu_view;
pub use settings::settings_view;
pub use skirmish::skirmish_view;
-127
View File
@@ -1,127 +0,0 @@
use crate::app::{App, keybindings::Action, widgets::KeybindingsWidget};
use ratatui::{
buffer::Buffer,
layout::{Alignment, Constraint, Layout, Rect},
style::{Style, Stylize},
text::Line,
widgets::{Block, Borders, Padding, Paragraph, Widget, Wrap},
};
pub fn settings_view(app: &App, area: Rect, buf: &mut Buffer) {
let vertical_layout: Layout = Layout::vertical([
Constraint::Length(4),
Constraint::Fill(1),
Constraint::Length(5),
]);
let [title_area, main_area, keybindings_area] = vertical_layout.areas(area);
{
Paragraph::new("War in tunnels")
.alignment(Alignment::Left)
.yellow()
.block(
Block::new()
.gray()
.borders(Borders::LEFT | Borders::TOP | Borders::RIGHT)
.padding(Padding::new(5, 1, 1, 1)),
)
.render(title_area, buf);
let lines: Vec<Line<'_>> = app
.states
.settings
.options
.iter()
.enumerate()
.map(|(i, o)| {
if i == app.states.settings.selected_setting {
Line::from_iter([
"> ".cyan(),
o.name.yellow(),
": ".gray(),
o.value.to_string().light_green(),
])
} else {
Line::from(format!(" {}: {}", o.name, o.value)).white()
}
})
.collect();
let settings_block: Block<'_> = Block::new()
.borders(Borders::LEFT | Borders::TOP | Borders::RIGHT)
.padding(Padding::new(1, 1, 1, 1))
.title(Line::from_iter([
"[ ".gray(),
"Settings".magenta(),
" ]".gray(),
]))
.gray();
let settings_area: Rect = settings_block.inner(main_area);
settings_block.render(main_area, buf);
Paragraph::new(lines).alignment(Alignment::Center).render(
settings_area.centered_vertically(Constraint::Ratio(1, 2)),
buf,
);
let popup_text: Vec<Line<'_>> = Vec::from_iter([
app.states
.settings
.selected_setting_new_value
.clone()
.into(),
app.states.settings.error_message.clone().into(),
]);
if app.states.settings.show_popup {
Paragraph::new(popup_text)
.wrap(Wrap { trim: true })
.style(Style::default())
.block(
Block::new()
.title("Insert new value")
.title_style(Style::default().yellow())
.borders(Borders::ALL)
.border_style(Style::default().gray())
.padding(Padding {
left: 1,
right: 1,
top: 1,
bottom: 1,
}),
)
.render(
Rect {
x: 2,
y: main_area.height - 1,
width: main_area.width - 3,
height: if app.states.settings.error_message.is_empty() {
5
} else {
6
},
},
buf,
);
}
}
{
let actions: Vec<Action> = vec![
Action::Up,
Action::Down,
Action::Quit,
Action::Quit2,
Action::Esc,
Action::Space,
Action::Enter,
Action::Backspace,
Action::WildCard('_'),
];
KeybindingsWidget::new(actions).render(keybindings_area, buf);
}
}
+89
View File
@@ -0,0 +1,89 @@
use crate::app::{App, keybindings::Action, widgets::KeybindingsWidget};
use ratatui::{
buffer::Buffer,
layout::{Alignment, Constraint, Layout, Margin, Rect},
style::Stylize,
text::Line,
widgets::{Block, Borders, Padding, Paragraph, Widget},
};
pub fn skirmish_view(app: &App, area: Rect, buf: &mut Buffer) {
let vertical_layout: Layout = Layout::vertical([
Constraint::Length(4),
Constraint::Fill(1),
Constraint::Length(4),
]);
let [title_area, main_area, keybindings_area] = vertical_layout.areas(area);
{
let lines: Vec<Line<'_>> = Vec::from_iter([
Line::raw("War in Tunnels").yellow(),
Line::from_iter([
format!("Wood: {} (+{}) | ", app.states.settings.starting_wood, 5),
format!("Iron: {} (+{}) | ", app.states.settings.starting_iron, 1),
format!(
"Supply Limit: {}/{} | ",
10, app.states.settings.supply_limit
),
format!(
"Skills points: {} ({}/{}) | ",
1, 20, app.states.settings.skill_points_limit
),
format!("Perk Deck: {}/9", 5),
]),
]);
Paragraph::new(lines)
.alignment(Alignment::Left)
.block(
Block::new()
.gray()
.padding(Padding {
left: 1,
right: 1,
top: 0,
bottom: 1,
})
.borders(Borders::LEFT | Borders::TOP | Borders::RIGHT),
)
.render(title_area, buf);
}
{
let board_block: Block = Block::new()
.gray()
.title(Line::from_iter([
"[ ".gray(),
"Skirmish".magenta(),
" - ".gray(),
"Map".green(),
" ]".gray(),
]))
.borders(Borders::LEFT | Borders::TOP | Borders::RIGHT);
let board_area: Rect = board_block.inner(main_area);
board_block.render(main_area, buf);
let inner_board_area: Rect = board_area.inner(Margin {
horizontal: 1,
vertical: 1,
});
let map_width: u16 = inner_board_area.width / (app.states.settings.map_width as u16);
let map_height: u16 = inner_board_area.height / (app.states.settings.map_height as u16);
Paragraph::new(format!(
"x = {}, y = {}, mw = {}, mh = {}",
inner_board_area.width, inner_board_area.height, map_width, map_height
))
.block(Block::default())
.render(inner_board_area, buf);
}
{
let actions: Vec<Action> = vec![Action::Quit, Action::Quit2, Action::Esc];
KeybindingsWidget::new(actions).render(keybindings_area, buf);
}
}
+3 -3
View File
@@ -45,7 +45,7 @@ pub struct Cli {
long,
help = "Map height",
value_name = "Positive integer [11; 50]",
default_value = "25"
default_value = "21"
)]
pub map_height: u8,
@@ -64,7 +64,7 @@ pub struct Cli {
value_name = "Positive integer",
default_value = "50"
)]
pub starting_wood: u8,
pub starting_wood: u16,
#[arg(
long,
@@ -72,7 +72,7 @@ pub struct Cli {
value_name = "Positive integer",
default_value = "25"
)]
pub starting_iron: u8,
pub starting_iron: u16,
#[arg(
long,