feat: 蛇形数据排列 + 8 掩码 + 评分
This commit is contained in:
+249
-1
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user