pyxel-engine 2.6.8

Core engine for Pyxel, a retro game engine for Python
macro_rules! shared_type {
    ($type: ty) => {
        std::sync::Arc<parking_lot::Mutex<$type>>
    }
}

macro_rules! new_shared_type {
    ($value: expr) => {
        std::sync::Arc::new(parking_lot::Mutex::new($value))
    };
}

macro_rules! string_loop {
    ($index: ident, $piece: ident, $string: ident, $step: expr, $block: block) => {
        for $index in 0..($string.len() / $step) {
            let __index = $index * $step;
            let $piece = $string[__index..__index + $step].to_string();
            $block
        }
    };
}

macro_rules! repeat_extend {
    ($container:expr, $value:expr, $count:expr) => {
        $container.extend(std::iter::repeat($value).take($count as usize));
    };
}

pub fn f32_to_i32(x: f32) -> i32 {
    x.round() as i32
}

pub fn f32_to_u32(x: f32) -> u32 {
    x.round() as u32
}

pub fn remove_whitespace(string: &str) -> String {
    string.replace(&[' ', '\n', '\r', '\t'][..], "")
}

pub fn simplify_string(string: &str) -> String {
    remove_whitespace(string).to_ascii_lowercase()
}

pub fn parse_hex_string(string: &str) -> Result<u32, &str> {
    let string = string.to_ascii_lowercase();
    let mut result: u32 = 0;

    for c in string.chars() {
        result *= 0x10;
        if c.is_ascii_digit() {
            result += c as u32 - '0' as u32;
        } else if ('a'..='f').contains(&c) {
            result += 10 + c as u32 - 'a' as u32;
        } else {
            return Err("invalid hex string");
        }
    }

    Ok(result)
}

pub fn add_file_extension(filename: &str, ext: &str) -> String {
    if filename.to_lowercase().ends_with(ext) {
        filename.to_string()
    } else {
        filename.to_string() + ext
    }
}

pub fn compress_vec<T: PartialEq + Clone>(vec: &[T]) -> Vec<T> {
    assert!(!vec.is_empty());
    let mut new_vec = vec.to_vec();
    let mut new_len = new_vec.len();

    for i in (1..new_vec.len()).rev() {
        if new_vec[i] == new_vec[i - 1] {
            new_len = i;
        } else {
            break;
        }
    }

    new_vec.truncate(new_len);
    new_vec
}

pub fn compress_vec2<T: PartialEq + Clone>(vec: &[Vec<T>]) -> Vec<Vec<T>> {
    assert!(!vec.is_empty());
    compress_vec(vec)
        .iter()
        .map(|inner_vec| compress_vec(inner_vec))
        .collect::<Vec<_>>()
}

pub fn expand_vec<T: Clone + Default>(vec: &[T], new_len: usize) -> Vec<T> {
    assert!(!vec.is_empty());
    let mut new_vec = vec.to_vec();

    if let Some(last) = new_vec.last().cloned() {
        new_vec.resize_with(new_len, move || last.clone());
    }

    new_vec
}

pub fn expand_vec2<T: Clone + Default>(
    vec: &[Vec<T>],
    new_outer_len: usize,
    new_inner_len: usize,
) -> Vec<Vec<T>> {
    assert!(!vec.is_empty());
    let new_vec = vec
        .iter()
        .map(|inner_vec| expand_vec(inner_vec, new_inner_len))
        .collect::<Vec<_>>();

    expand_vec(&new_vec, new_outer_len)
}

pub fn trim_empty_vecs<T: Clone>(vecs: &[Vec<T>]) -> Vec<Vec<T>> {
    let mut vecs = vecs.to_vec();
    let new_len = vecs
        .iter()
        .rev()
        .position(|vec| !vec.is_empty())
        .map_or(0, |i| vecs.len() - i);

    vecs.truncate(new_len);
    vecs
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_string_loop() {
        let test_string = "TEST_STRING";
        string_loop!(i, value, test_string, 1, {
            match i {
                3 => {
                    assert_eq!(value, "T");
                }
                7 => {
                    assert_eq!(value, "R");
                }
                _ => {}
            }
        });

        string_loop!(i, value, test_string, 3, {
            match i {
                0 => {
                    assert_eq!(value, "TES");
                }
                2 => {
                    assert_eq!(value, "TRI");
                }
                _ => {}
            }
        });
    }

    #[test]
    fn test_f32_to_i32() {
        assert_eq!(f32_to_i32(0.1), 0);
        assert_eq!(f32_to_i32(0.49), 0);
        assert_eq!(f32_to_i32(0.5), 1);
        assert_eq!(f32_to_i32(1.49), 1);
        assert_eq!(f32_to_i32(-0.1), 0);
        assert_eq!(f32_to_i32(-0.49), 0);
        assert_eq!(f32_to_i32(-0.50), -1);
        assert_eq!(f32_to_i32(-1.49), -1);
    }

    #[test]
    fn test_f32_to_u32_() {
        assert_eq!(f32_to_u32(0.1), 0);
        assert_eq!(f32_to_u32(0.49), 0);
        assert_eq!(f32_to_u32(0.5), 1);
        assert_eq!(f32_to_u32(1.49), 1);
        assert_eq!(f32_to_u32(-0.1), 0);
        assert_eq!(f32_to_u32(-3.0), 0);
    }

    #[test]
    fn test_remove_whitespace() {
        assert_eq!(remove_whitespace(" a\n b\r c\t d "), "abcd");
    }

    #[test]
    fn test_simplify_string() {
        assert_eq!(simplify_string(" 0\n 1\r 2\t 3 A\n b\r c\t d "), "0123abcd");
    }

    #[test]
    fn test_parse_hex_string() {
        assert_eq!(parse_hex_string("100"), Ok(256));
        assert_eq!(parse_hex_string("a2"), Ok(162));
        assert_eq!(parse_hex_string("BC"), Ok(188));
        assert_eq!(parse_hex_string(" "), Err("invalid hex string"));
    }

    #[test]
    fn test_compress_vec() {
        let vec = vec![1, 2, 2, 3, 3, 3];
        let result = compress_vec(&vec);
        assert_eq!(result, vec![1, 2, 2, 3]);

        let vec = vec![4, 4, 4, 4, 4];
        let result = compress_vec(&vec);
        assert_eq!(result, vec![4]);

        let vec: Vec<i32> = vec![2];
        let result = compress_vec(&vec);
        assert_eq!(result, vec![2]);
    }

    #[test]
    fn test_compress_vec2() {
        let vec = vec![vec![1, 1, 2], vec![2, 2, 2], vec![3, 3, 3], vec![3, 3, 3]];
        let result = compress_vec2(&vec);
        assert_eq!(result, vec![vec![1, 1, 2], vec![2], vec![3]]);

        let vec = vec![vec![4, 4, 4], vec![4, 4, 4], vec![5, 5, 5]];
        let result = compress_vec2(&vec);
        assert_eq!(result, vec![vec![4], vec![4], vec![5]]);

        let vec: Vec<Vec<i32>> = vec![vec![2]];
        let result = compress_vec2(&vec);
        assert_eq!(result, vec![vec![2]]);
    }

    #[test]
    fn test_expand_vec() {
        let vec = vec![1, 2, 3];
        let result = expand_vec(&vec, 5);
        assert_eq!(result, vec![1, 2, 3, 3, 3]);

        let vec = vec![4];
        let result = expand_vec(&vec, 3);
        assert_eq!(result, vec![4, 4, 4]);

        let vec: Vec<i32> = vec![1, 2, 3];
        let result = expand_vec(&vec, 2);
        assert_eq!(result, vec![1, 2]);
    }

    #[test]
    fn test_expand_vec2() {
        let vec = vec![vec![1, 2], vec![3]];
        let result = expand_vec2(&vec, 4, 3);
        assert_eq!(
            result,
            vec![vec![1, 2, 2], vec![3, 3, 3], vec![3, 3, 3], vec![3, 3, 3]]
        );

        let vec = vec![vec![4]];
        let result = expand_vec2(&vec, 3, 2);
        assert_eq!(result, vec![vec![4, 4], vec![4, 4], vec![4, 4]]);

        let vec = vec![
            vec![1, 2, 4],
            vec![4, 5, 6],
            vec![7, 8, 9],
            vec![10, 11, 12],
        ];
        let result = expand_vec2(&vec, 3, 2);
        assert_eq!(result, vec![vec![1, 2], vec![4, 5], vec![7, 8]]);
    }

    #[test]
    fn test_trim_empty_vecs() {
        let vecs = vec![vec![1, 2], vec![], vec![], vec![3, 4], vec![], vec![]];
        assert_eq!(
            trim_empty_vecs(&vecs),
            vec![vec![1, 2], vec![], vec![], vec![3, 4]]
        );

        let vecs: Vec<Vec<i32>> = vec![vec![], vec![]];
        assert_eq!(trim_empty_vecs(&vecs), Vec::<Vec<i32>>::new());

        let vecs = vec![vec![1], vec![2]];
        assert_eq!(trim_empty_vecs(&vecs), vec![vec![1], vec![2]]);

        let vecs: Vec<Vec<i32>> = vec![vec![]];
        assert_eq!(trim_empty_vecs(&vecs), Vec::<Vec<i32>>::new());

        let vecs = vec![vec![1, 2]];
        assert_eq!(trim_empty_vecs(&vecs), vec![vec![1, 2]]);
    }
}