Add tests, fix offset logic, and bump dependencies

- Introduce a comprehensive test suite (cli, app, board, tasks, audio)
  and a
  new TESTING.md document describing test execution.
- Correct vertical offset calculation in `BoardState` to use saturating
  subtraction and ceiling division.
- Clamp initial `Offset` values to the provided maximum in
  `Offset::new`.
- Export `load_audio` from the audio thread module and switch to
  `LoopedDecoder` for proper OGG looping.
- Update several crate versions: clap, ratatui, log, rodio, and
  bitflags.
This commit is contained in:
GarandPLG committed 2026-10-18 20:29:04 +02:00
1 parent 9bc42dc898
commit fcdbbaf74c
16 files changed
+817 -246

No files matched your search

+1 -1
View File
@@ -51,7 +51,7 @@ impl BoardState {
let h_max_offset: usize = Self::max_offset(map_width, cols);
let vertical_offset: Offset = Offset::new(
Some((map_height - rows as usize + 1) / 2),
Some(map_height.saturating_sub(rows).div_ceil(2)),
Some(v_max_offset),
);
let horizontal_offset: Offset = Offset::new(None, Some(h_max_offset));
+3 -2
View File
@@ -7,7 +7,7 @@ pub struct Offset {
impl Offset {
pub fn new(initial_value: Option<usize>, initial_max: Option<usize>) -> Self {
Self {
value: initial_value.unwrap_or(0),
value: initial_value.unwrap_or(0).min(initial_max.unwrap_or(0)),
max: initial_max.unwrap_or(0),
}
}
@@ -17,11 +17,12 @@ impl Offset {
}
pub fn set_value(&mut self, value: usize) {
self.value = value;
self.value = value.min(self.max);
}
pub fn set_max(&mut self, max: usize) {
self.max = max;
self.value = self.value.min(max);
}
pub fn next(&mut self) {
@@ -1,5 +1,5 @@
use crate::app::states::skirmish_states::tasks::Task;
use std::{collections::VecDeque, ops::Add};
use std::collections::VecDeque;
#[derive(Debug, Clone, PartialEq)]
pub struct DiggingTask {
@@ -49,7 +49,7 @@ impl Task for DiggingTask {
}
fn progress(&mut self) {
let _ = self.progress.add(self.tick_increase).max(0.0).min(100.0);
self.progress = (self.progress + self.tick_increase).clamp(0.0, 100.0);
}
fn is_finished(&self) -> bool {
+29 -34
View File
@@ -1,7 +1,8 @@
use clap::ValueEnum;
use once_cell::sync::Lazy;
use rodio::{
Decoder, DeviceSinkBuilder, MixerDeviceSink, Player, Source, cpal::BufferSize, source::Amplify,
Decoder, DeviceSinkBuilder, MixerDeviceSink, Player, Source, cpal::BufferSize,
decoder::LoopedDecoder, source::Amplify,
};
use std::{
collections::HashMap,
@@ -69,10 +70,10 @@ fn load_soundtrack_folder(folder: &str) -> SoundtrackCache {
cache
}
fn load_audio(
pub fn load_audio(
soundtrack: &Soundtrack,
part: &SoundrackParts,
) -> Amplify<Decoder<BufReader<Cursor<Vec<u8>>>>> {
) -> Amplify<LoopedDecoder<BufReader<Cursor<Vec<u8>>>>> {
{
let guard: RwLockReadGuard<'_, HashMap<Soundtrack, Arc<HashMap<SoundrackParts, Vec<u8>>>>> =
PRELOADED_SOUNDTRACKS
@@ -86,7 +87,7 @@ fn load_audio(
if let Some(soundtrack_cache) = guard.get(soundtrack) {
let data: &Vec<u8> = soundtrack_cache.get(part).expect("part missing in cache");
return Decoder::new(BufReader::new(Cursor::new(data.clone())))
return Decoder::new_looped(BufReader::new(Cursor::new(data.clone())))
.expect("decoder issue")
.amplify(0.20);
}
@@ -107,7 +108,7 @@ fn load_audio(
if let Some(soundtrack_cache) = guard.get(soundtrack) {
let data: &Vec<u8> = soundtrack_cache.get(part).expect("part missing in cache");
return Decoder::new(BufReader::new(Cursor::new(data.clone())))
return Decoder::new_looped(BufReader::new(Cursor::new(data.clone())))
.expect("decoder issue")
.amplify(0.20);
}
@@ -123,7 +124,7 @@ fn load_audio(
guard.insert(*soundtrack, Arc::clone(&new_cache));
Decoder::new(BufReader::new(Cursor::new(data.clone())))
Decoder::new_looped(BufReader::new(Cursor::new(data.clone())))
.expect("decoder issue")
.amplify(0.20)
}
@@ -146,35 +147,29 @@ pub fn handle_audio(rx: Receiver<AudioCmd>, mute: bool, soundtrack: Soundtrack)
let mut current_part: SoundrackParts = SoundrackParts::Calm;
player.append(load_audio(&current_soundtrack, &current_part));
loop {
for cmd in rx.try_iter() {
match cmd {
AudioCmd::Mute => {
player.set_volume(if player.volume() == 0.0 { volume } else { 0.0 });
}
AudioCmd::VolumeUp => {
volume = (volume + 0.1).min(1.0);
player.set_volume(volume);
}
AudioCmd::VolumeDown => {
volume = (volume - 0.1).max(0.0);
player.set_volume(volume);
}
AudioCmd::ChangePart(part) => {
current_part = part;
player.append(load_audio(&current_soundtrack, &current_part));
player.skip_one();
}
AudioCmd::ChangeSoundtrack(st) => {
current_soundtrack = st;
player.append(load_audio(&current_soundtrack, &current_part));
player.skip_one();
}
for cmd in rx {
match cmd {
AudioCmd::Mute => {
player.set_volume(if player.volume() == 0.0 { volume } else { 0.0 });
}
AudioCmd::VolumeUp => {
volume = (volume + 0.1).min(1.0);
player.set_volume(volume);
}
AudioCmd::VolumeDown => {
volume = (volume - 0.1).max(0.0);
player.set_volume(volume);
}
AudioCmd::ChangePart(part) => {
current_part = part;
player.append(load_audio(&current_soundtrack, &current_part));
player.skip_one();
}
AudioCmd::ChangeSoundtrack(st) => {
current_soundtrack = st;
player.append(load_audio(&current_soundtrack, &current_part));
player.skip_one();
}
}
if player.empty() {
player.append(load_audio(&current_soundtrack, &current_part));
}
}
}
+1 -1
View File
@@ -2,6 +2,6 @@ mod audio;
mod events;
mod tick;
pub use audio::{AudioCmd, SoundrackParts, Soundtrack, handle_audio};
pub use audio::{AudioCmd, SoundrackParts, Soundtrack, handle_audio, load_audio};
pub use events::handle_ct_events;
pub use tick::{TICK_MS, handle_tick_event};