use bevy::prelude::*;
pub const TARGET_ROWS: usize = 20;
pub const TARGET_COLS: usize = 20;
pub const SIMPLE_BRICK: u8 = 20;
pub const INDESTRUCTIBLE_BRICK: u8 = 90;
pub const MULTI_HIT_BRICK_1: u8 = 10;
pub const MULTI_HIT_BRICK_2: u8 = 11;
pub const MULTI_HIT_BRICK_3: u8 = 12;
pub const MULTI_HIT_BRICK_4: u8 = 13;
#[inline]
pub fn is_multi_hit_brick(type_id: u8) -> bool {
(MULTI_HIT_BRICK_1..=MULTI_HIT_BRICK_4).contains(&type_id)
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct NormalizationMetrics {
pub padded_rows: usize,
pub truncated_rows: usize,
pub padded_cols: usize,
pub truncated_cols: usize,
}
#[derive(Debug, Clone)]
pub struct NormalizationResult {
pub matrix: Vec<Vec<u8>>,
pub metrics: NormalizationMetrics,
}
pub fn normalize_matrix(mut matrix: Vec<Vec<u8>>) -> NormalizationResult {
let mut metrics = NormalizationMetrics::default();
let original_rows = matrix.len();
let original_cols = matrix.first().map_or(0, |r| r.len());
if original_rows != TARGET_ROWS || original_cols != TARGET_COLS {
warn!(
"Level matrix wrong dimensions; expected 20x20, got {}x{}",
original_rows, original_cols
);
}
if matrix.len() < TARGET_ROWS {
metrics.padded_rows = TARGET_ROWS - matrix.len();
while matrix.len() < TARGET_ROWS {
matrix.push(vec![0; TARGET_COLS]);
}
}
if matrix.len() > TARGET_ROWS {
metrics.truncated_rows = matrix.len() - TARGET_ROWS;
warn!(
"Level matrix has {} rows; truncating to {}",
matrix.len(),
TARGET_ROWS
);
matrix.truncate(TARGET_ROWS);
}
for (i, row) in matrix.iter_mut().enumerate() {
let original_row_len = row.len();
if row.len() < TARGET_COLS {
metrics.padded_cols += TARGET_COLS - row.len();
while row.len() < TARGET_COLS {
row.push(0);
}
}
if original_row_len > TARGET_COLS {
metrics.truncated_cols += original_row_len - TARGET_COLS;
warn!(
"Row {} has {} columns; truncating to {}",
i, original_row_len, TARGET_COLS
);
row.truncate(TARGET_COLS);
}
}
NormalizationResult { matrix, metrics }
}
pub fn normalize_matrix_simple(matrix: Vec<Vec<u8>>) -> Vec<Vec<u8>> {
normalize_matrix(matrix).matrix
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn normalize_padding_rows_and_cols() {
let input = vec![vec![1u8; 19]; 18];
let result = normalize_matrix(input.clone());
assert_eq!(result.matrix.len(), 20, "row count padded to 20");
for row in &result.matrix {
assert_eq!(row.len(), 20, "col count padded to 20");
}
assert_eq!(result.metrics.padded_rows, 2);
assert_eq!(result.metrics.truncated_rows, 0);
assert_eq!(result.metrics.padded_cols, 18);
assert_eq!(result.metrics.truncated_cols, 0);
for (r, row) in result.matrix.iter().enumerate().take(18) {
for (c, &val) in row.iter().enumerate().take(19) {
assert_eq!(val, 1, "row {r} col {c} should preserve value 1");
}
}
for (r, row) in result.matrix.iter().enumerate().take(18) {
assert_eq!(row[19], 0, "row {r} col 19 should be padded zero");
}
for (r, row) in result.matrix.iter().enumerate().skip(18).take(2) {
for (c, &val) in row.iter().enumerate() {
assert_eq!(val, 0, "row {r} col {c} should be padded zero");
}
}
}
#[test]
fn normalize_truncates_rows_and_cols() {
let input = vec![vec![2u8; 24]; 22];
let result = normalize_matrix(input.clone());
assert_eq!(result.matrix.len(), 20);
for row in &result.matrix {
assert_eq!(row.len(), 20);
}
assert_eq!(result.metrics.padded_rows, 0);
assert_eq!(result.metrics.truncated_rows, 2);
assert_eq!(result.metrics.padded_cols, 0);
assert_eq!(result.metrics.truncated_cols, 80);
for r in 0..20 {
for c in 0..20 {
assert_eq!(result.matrix[r][c], 2);
}
}
}
#[test]
fn normalize_irregular_row_lengths() {
let mut input: Vec<Vec<u8>> = Vec::new();
for i in 0..22 {
let len = match i % 3 {
0 => 10,
1 => 25,
_ => 20,
}; input.push(vec![3u8; len]);
}
let result = normalize_matrix(input);
assert_eq!(result.matrix.len(), 20);
for (r, row) in result.matrix.iter().enumerate() {
assert_eq!(row.len(), 20, "row {} not normalized to 20 cols", r);
let original_len = match r % 3 {
0 => 10,
1 => 25,
_ => 20,
};
let preserved = original_len.min(20);
for (c, &val) in row.iter().enumerate().take(preserved) {
assert_eq!(val, 3, "row {r} col {c} should preserve value 3");
}
for (c, &val) in row.iter().enumerate().skip(preserved).take(20 - preserved) {
assert_eq!(val, 0, "row {r} col {c} should be padded zero");
}
}
assert_eq!(result.metrics.truncated_rows, 2);
assert_eq!(result.metrics.padded_rows, 0);
assert_eq!(result.metrics.padded_cols, 70);
assert_eq!(result.metrics.truncated_cols, 35);
}
#[test]
fn normalize_empty_matrix() {
let result = normalize_matrix(Vec::new());
assert_eq!(result.matrix.len(), 20);
for row in &result.matrix {
assert_eq!(row.len(), 20);
for c in row {
assert_eq!(*c, 0);
}
}
assert_eq!(result.metrics.padded_rows, 20);
assert_eq!(result.metrics.truncated_rows, 0);
assert_eq!(result.metrics.padded_cols, 0); assert_eq!(result.metrics.truncated_cols, 0);
}
#[test]
fn normalize_exact_dimensions_unchanged() {
let mut input = vec![vec![5u8; 20]; 20];
input[0][0] = 7;
let result = normalize_matrix(input.clone());
assert_eq!(result.matrix.len(), 20);
for row in &result.matrix {
assert_eq!(row.len(), 20);
}
assert_eq!(result.matrix[0][0], 7);
for (r, row) in result.matrix.iter().enumerate().take(20) {
for (c, &val) in row.iter().enumerate().take(20) {
assert_eq!(val, input[r][c]);
}
}
assert_eq!(result.metrics.padded_rows, 0);
assert_eq!(result.metrics.truncated_rows, 0);
assert_eq!(result.metrics.padded_cols, 0);
assert_eq!(result.metrics.truncated_cols, 0);
}
#[test]
fn metrics_track_individual_row_padding() {
let mut input: Vec<Vec<u8>> = Vec::new();
for i in 0..20 {
let len = if i < 10 { 15 } else { 20 }; input.push(vec![1u8; len]);
}
let result = normalize_matrix(input);
assert_eq!(result.metrics.padded_rows, 0);
assert_eq!(result.metrics.padded_cols, 50); assert_eq!(result.metrics.truncated_rows, 0);
assert_eq!(result.metrics.truncated_cols, 0);
}
#[test]
fn metrics_track_individual_row_truncation() {
let mut input: Vec<Vec<u8>> = Vec::new();
for i in 0..20 {
let len = if i < 5 { 25 } else { 20 }; input.push(vec![1u8; len]);
}
let result = normalize_matrix(input);
assert_eq!(result.metrics.padded_rows, 0);
assert_eq!(result.metrics.padded_cols, 0);
assert_eq!(result.metrics.truncated_rows, 0);
assert_eq!(result.metrics.truncated_cols, 25); }
}