feat: 蛇形数据排列 + 8 掩码 + 评分

This commit is contained in:
2026-06-16 23:47:21 +08:00
parent 1adb7e41e4
commit 6e1980696d
2 changed files with 329 additions and 2 deletions
+249 -1
View File
@@ -1 +1,249 @@
// FIXME: 掩码评分 — Task 8
use crate::matrix::grid::Matrix;
/// 掩码函数: f(x, y) = true 时翻转该模块
type MaskFn = fn(u8, u8) -> bool;
/// 8 种标准 QR 掩码
pub const MASK_FNS: [MaskFn; 8] = [
|x, y| (x + y) % 2 == 0,
|_, y| y % 2 == 0,
|x, _| x % 3 == 0,
|x, y| (x + y) % 3 == 0,
|x, y| ((y / 2) + (x / 3)) % 2 == 0,
|x, y| (x as u32 * y as u32) % 2 + (x as u32 * y as u32) % 3 == 0,
|x, y| ((x as u32 * y as u32) % 2 + (x as u32 * y as u32) % 3) % 2 == 0,
|x, y| ((x as u32 + y as u32) % 2 + (x as u32 * y as u32) % 3) % 2 == 0,
];
/// 应用掩码到矩阵的数据区域(跳过功能图案保留区域)
pub fn apply_mask(matrix: &Matrix, mask_idx: u8) -> Matrix {
let mask_fn = MASK_FNS[mask_idx as usize];
let mut result = matrix.clone();
for y in 0..matrix.size {
for x in 0..matrix.size {
if !matrix.is_reserved(x, y) && mask_fn(x, y) {
let current = result.get(x, y);
result.set(x, y, !current);
}
}
}
result
}
/// 惩罚评分(越低越好)
pub fn score(matrix: &Matrix) -> u32 {
score_rule1(matrix) + score_rule2(matrix) + score_rule3(matrix) + score_rule4(matrix)
}
/// 规则 1: 连续 5+ 同色行/列 → N1 + k - 5
fn score_rule1(matrix: &Matrix) -> u32 {
let mut penalty = 0u32;
let n = matrix.size as usize;
// 水平扫描
for y in 0..n {
let mut run = 1u32;
let mut prev = matrix.get(0, y as u8);
for x in 1..n {
let cur = matrix.get(x as u8, y as u8);
if cur == prev {
run += 1;
} else {
if run >= 5 {
penalty += 3 + run - 5;
}
run = 1;
prev = cur;
}
}
if run >= 5 {
penalty += 3 + run - 5;
}
}
// 垂直扫描
for x in 0..n {
let mut run = 1u32;
let mut prev = matrix.get(x as u8, 0);
for y in 1..n {
let cur = matrix.get(x as u8, y as u8);
if cur == prev {
run += 1;
} else {
if run >= 5 {
penalty += 3 + run - 5;
}
run = 1;
prev = cur;
}
}
if run >= 5 {
penalty += 3 + run - 5;
}
}
penalty
}
/// 规则 2: 同色 2×2 方块,每个 +3
fn score_rule2(matrix: &Matrix) -> u32 {
let mut count = 0u32;
let n = matrix.size;
for y in 0..n - 1 {
for x in 0..n - 1 {
let v = matrix.get(x, y);
if matrix.get(x + 1, y) == v
&& matrix.get(x, y + 1) == v
&& matrix.get(x + 1, y + 1) == v
{
count += 1;
}
}
}
count * 3
}
/// 规则 3: 检测 1011101 模式(及其反转),每次 +40
fn score_rule3(matrix: &Matrix) -> u32 {
let mut penalty = 0u32;
let n = matrix.size as usize;
// 水平方向
for y in 0..n {
for x in 0..n {
if x + 10 >= n {
continue;
}
// 正模式: 10111010000
let forward = (0..11).all(|i| {
let expected = [
true, false, true, true, true, false, true, false, false, false, false,
][i];
matrix.get((x + i) as u8, y as u8) == expected
});
if forward {
penalty += 40;
}
// 反模式: 00001011101
let reverse = (0..11).all(|i| {
let expected = [
false, false, false, false, true, false, true, true, true, false, true,
][i];
matrix.get((x + i) as u8, y as u8) == expected
});
if reverse {
penalty += 40;
}
}
}
// 垂直方向
for y in 0..n {
if y + 10 >= n {
continue;
}
for x in 0..n {
let forward = (0..11).all(|i| {
let expected = [
true, false, true, true, true, false, true, false, false, false, false,
][i];
matrix.get(x as u8, (y + i) as u8) == expected
});
if forward {
penalty += 40;
}
let reverse = (0..11).all(|i| {
let expected = [
false, false, false, false, true, false, true, true, true, false, true,
][i];
matrix.get(x as u8, (y + i) as u8) == expected
});
if reverse {
penalty += 40;
}
}
}
penalty
}
/// 规则 4: 暗模块占比偏离 50%,每 5% +10
fn score_rule4(matrix: &Matrix) -> u32 {
let total = (matrix.size as u32) * (matrix.size as u32);
let dark: u32 = (0..matrix.size)
.flat_map(|y| (0..matrix.size).map(move |x| matrix.get(x, y) as u32))
.sum();
let pct = (dark * 100 + total / 2) / total; // 四舍五入
let deviation = ((pct as i32 - 50).unsigned_abs()) / 5;
deviation * 10
}
/// 评估所有 8 种掩码,返回最佳掩码编号和对应矩阵
pub fn best_mask(matrix: &Matrix) -> (u8, Matrix) {
let mut best_idx = 0u8;
let mut best_score = u32::MAX;
let mut best_matrix = matrix.clone();
for i in 0..8u8 {
let masked = apply_mask(matrix, i);
let s = score(&masked);
if s < best_score {
best_score = s;
best_idx = i;
best_matrix = masked;
}
}
(best_idx, best_matrix)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_apply_mask() {
let m = Matrix::new(21);
// 不设置 reserved,所有区域都是数据区
let masked = apply_mask(&m, 0); // (x+y) % 2 == 0
// 初始全白,掩码 0 会在 (x+y)%2==0 的位置翻转
assert_eq!(masked.get(0, 0), true); // (0+0)%2=0 → 翻转
assert_eq!(masked.get(1, 0), false); // (1+0)%2=1 → 不变
}
#[test]
fn test_score_rule2() {
let mut m = Matrix::new(3);
// 全黑 → 4 个 2×2 方块
for y in 0..3u8 {
for x in 0..3u8 {
m.set(x, y, true);
}
}
assert_eq!(score_rule2(&m), 4 * 3); // 4 blocks × 3 = 12
}
#[test]
fn test_score_rule4() {
let m = Matrix::new(10);
// 全部白色 → 0% dark → 偏离 50% = 10 × 5% → penalty = 10 × 10 = 100
let s = score_rule4(&m);
assert_eq!(s, 100);
}
#[test]
fn test_best_mask_selects_something() {
let mut m = Matrix::new(21);
// 填一些随机数据
for y in 0..21u8 {
for x in 0..21u8 {
m.set(x, y, (x as u32 * y as u32) % 3 == 0);
}
}
let (idx, _masked) = best_mask(&m);
assert!(idx < 8);
}
}
+80 -1
View File
@@ -1 +1,80 @@
// FIXME: 数据排列 — Task 8
use crate::matrix::grid::Matrix;
/// 将码字比特按蛇形路径放入矩阵数据区域
pub fn place_data(matrix: &mut Matrix, codewords: &[u8]) {
let size = matrix.size as usize;
// 展开字节为比特流
let bits: Vec<bool> = codewords
.iter()
.flat_map(|&cw| (0..8).rev().map(move |i| (cw >> i) & 1 == 1))
.collect();
let mut bit_idx = 0usize;
// 从右下角开始,向上扫描
let mut col = (size - 1) as i16;
let mut going_up = true;
while col >= 0 && bit_idx < bits.len() {
// 跳过垂直时序线 (col=6)
let actual_col = if col == 6 { 5 } else { col as usize };
if going_up {
for row in (0..size).rev() {
if bit_idx >= bits.len() {
break;
}
place_bit(
matrix,
actual_col as u8,
row as u8,
bits[bit_idx],
&mut bit_idx,
);
if bit_idx >= bits.len() {
break;
}
place_bit(
matrix,
(actual_col - 1) as u8,
row as u8,
bits[bit_idx],
&mut bit_idx,
);
}
} else {
for row in 0..size {
if bit_idx >= bits.len() {
break;
}
place_bit(
matrix,
actual_col as u8,
row as u8,
bits[bit_idx],
&mut bit_idx,
);
if bit_idx >= bits.len() {
break;
}
place_bit(
matrix,
(actual_col - 1) as u8,
row as u8,
bits[bit_idx],
&mut bit_idx,
);
}
}
col -= 2;
going_up = !going_up;
}
}
fn place_bit(matrix: &mut Matrix, x: u8, y: u8, bit: bool, idx: &mut usize) {
if x < matrix.size && y < matrix.size && !matrix.is_reserved(x, y) {
matrix.set(x, y, bit);
*idx += 1;
}
}