https://github.com/JozefJarosciak/TangramPuzzleSolver 是一个图形界面的俄罗斯方块覆盖问题求解器,为了能够批量求解,让DeepSeek提取其中的计算代码,和字符串输入组成一个命令行工具。
将附件javascript代码提取出与显示无关的后台计算代码,读入一行 矩形长度,矩形宽度,[形状编号(A、B、C、D、I、L等),数量 ]列表,有解,则输出填充了形状编号的矩阵,无解提示无解
他完全理解了需求,输出代码如下。
// block_solver_core.js
// 俄罗斯方块覆盖问题求解器 - 核心计算模块
// ============================
// 配置常量
// ============================
// 方块形状定义
const BLOCKS = {
'A': [ // Monomino (1x1)
[[1]]
],
'B': [ // Domino (2x1)
[[1,1]], // Horizontal
[[1],[1]] // Vertical
],
'C': [ // Triomino I (3x1)
[[1,1,1]], // Horizontal
[[1],[1],[1]] // Vertical
],
'D': [ // Triomino L
[[1,0],
[1,1]],
[[0,1],
[1,1]],
[[1,1],
[1,0]],
[[1,1],
[0,1]]
],
'I': [ // Tetromino I (4x1)
[[1,1,1,1]],
[[1],
[1],
[1],
[1]]
],
'L': [ // Tetromino L
[[1,1,1],
[1,0,0]],
[[1,0],
[1,0],
[1,1]],
[[0,0,1],
[1,1,1]],
[[1,1],
[0,1],
[0,1]]
],
'O': [ // Tetromino O (2x2)
[[1,1],
[1,1]]
],
'S': [ // Tetromino S
[[0,1,1],
[1,1,0]],
[[1,0],
[1,1],
[0,1]]
],
'T': [ // Tetromino T
[[1,1,1],
[0,1,0]],
[[0,1,0],
[1,1,1]],
[[1,0],
[1,1],
[1,0]],
[[0,1],
[1,1],
[0,1]]
],
'U': [ // Pentomino U
[[1,0,1],
[1,1,1]],
[[1,1,1],
[1,0,1]],
[[1,1],
[1,0],
[1,1]],
[[1,1],
[0,1],
[1,1]]
],
'X': [ // Pentomino X
[[0,1,0],
[1,1,1],
[0,1,0]]
]
};
// 方块尺寸映射
const BLOCK_SIZES = {
'A': 1, 'B': 2, 'C': 3, 'D': 3,
'I': 4, 'L': 4, 'O': 4, 'S': 4,
'T': 4, 'U': 5, 'X': 5
};
// ============================
// 工具函数
// ============================
/**
* 打乱数组顺序
* @param {Array} array - 要打乱的数组
*/
function shuffle(array) {
for (let i = array.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[array[i], array[j]] = [array[j], array[i]];
}
}
/**
* 水平翻转形状
* @param {Array<Array<number>>} shape - 原始形状
* @returns {Array<Array<number>>} - 翻转后的形状
*/
function reflectShape(shape) {
return shape.map(row => [...row].reverse());
}
/**
* 获取方块在网格上的所有可能放置位置
* @param {Array<Array<number>>} shape - 方块形状
* @param {number} gridWidth - 网格宽度
* @param {number} gridHeight - 网格高度
* @returns {Array<Array<string>>} - 所有可能的放置位置(每个位置是一个单元格坐标数组)
*/
function getAllPlacements(shape, gridWidth, gridHeight) {
if (!shape || shape.length === 0) {
return [];
}
const placements = [];
const blockHeight = shape.length;
const blockWidth = shape[0].length;
for (let y = 0; y <= gridHeight - blockHeight; y++) {
for (let x = 0; x <= gridWidth - blockWidth; x++) {
const cells = [];
for (let i = 0; i < blockHeight; i++) {
for (let j = 0; j < blockWidth; j++) {
if (shape[i][j] === 1) {
cells.push(`${y + i},${x + j}`);
}
}
}
placements.push(cells);
}
}
return placements;
}
// ============================
// 舞蹈链算法实现
// ============================
class DancingLinksNode {
constructor() {
this.left = this;
this.right = this;
this.up = this;
this.down = this;
this.column = null;
}
}
class ColumnNode extends DancingLinksNode {
constructor(name) {
super();
this.name = name;
this.size = 0;
this.column = this;
}
/**
* 覆盖这一列
*/
cover() {
this.right.left = this.left;
this.left.right = this.right;
let i = this.down;
while (i !== this) {
let j = i.right;
while (j !== i) {
j.down.up = j.up;
j.up.down = j.down;
j.column.size--;
j = j.right;
}
i = i.down;
}
}
/**
* 恢复这一列
*/
uncover() {
let i = this.up;
while (i !== this) {
let j = i.left;
while (j !== i) {
j.column.size++;
j.down.up = j;
j.up.down = j;
j = j.left;
}
i = i.up;
}
this.right.left = this;
this.left.right = this;
}
}
class DataNode extends DancingLinksNode {
constructor(column) {
super();
this.column = column;
}
}
class DancingLinksSolver {
constructor(columns, maxTimeMs = 10000) {
this.header = new ColumnNode('header');
this.columns = {};
this.startTime = performance.now();
this.maxTimeMs = maxTimeMs;
this.found = false;
// 初始化列节点
let last = this.header;
columns.forEach(colName => {
const column = new ColumnNode(colName);
this.columns[colName] = column;
last.right = column;
column.left = last;
last = column;
});
last.right = this.header;
this.header.left = last;
}
/**
* 搜索解
* @param {number} k - 当前搜索深度
* @param {Array} solution - 当前解决方案
* @param {Array} solutions - 所有找到的解决方案
*/
search(k, solution, solutions) {
// 检查超时
if (performance.now() - this.startTime > this.maxTimeMs) {
return;
}
// 如果所有列都被覆盖,找到解
if (this.header.right === this.header) {
solutions.push([...solution]);
this.found = true;
return;
}
// 选择最小约束的列
let min = Infinity;
let chosenColumn = null;
let tmp = this.header.right;
while (tmp !== this.header) {
if (tmp.size < min) {
min = tmp.size;
chosenColumn = tmp;
}
tmp = tmp.right;
}
if (chosenColumn === null || chosenColumn.size === 0) {
return;
}
chosenColumn.cover();
// 收集该列的所有行
let row = chosenColumn.down;
const rows = [];
while (row !== chosenColumn) {
rows.push(row);
row = row.down;
}
// 打乱行顺序以获得随机解
shuffle(rows);
for (let i = 0; i < rows.length && !this.found; i++) {
row = rows[i];
solution.push(row);
// 覆盖该行涉及的所有列
let j = row.right;
while (j !== row) {
j.column.cover();
j = j.right;
}
// 递归搜索
this.search(k + 1, solution, solutions);
// 回溯
solution.pop();
j = row.left;
while (j !== row) {
j.column.uncover();
j = j.left;
}
}
chosenColumn.uncover();
}
}
// ============================
// 主要求解函数
// ============================
/**
* 解析输入字符串
* @param {string} inputStr - 输入字符串,格式:width,height,[shape,count...]
* @returns {Object} - 解析后的参数对象
*/
function parseInput(inputStr) {
// 移除所有空格
const cleaned = inputStr.replace(/\s+/g, '');
// 匹配矩形尺寸
const sizeMatch = cleaned.match(/^(\d+),(\d+),\[/);
if (!sizeMatch) {
throw new Error('输入格式错误: 应以 "width,height,[" 开头');
}
const width = parseInt(sizeMatch[1]);
const height = parseInt(sizeMatch[2]);
// 提取方块列表部分
const listMatch = cleaned.match(/\[(.*)\]/);
if (!listMatch) {
throw new Error('输入格式错误: 缺少方块列表');
}
const blockListStr = listMatch[1];
const blockCounts = {};
// 解析方块列表
const blockPattern = /([A-Z]),(\d+)/g;
let match;
let totalArea = 0;
while ((match = blockPattern.exec(blockListStr)) !== null) {
const shape = match[1];
const count = parseInt(match[2]);
if (!BLOCKS[shape]) {
throw new Error(`未知方块类型: ${shape}`);
}
if (blockCounts[shape]) {
blockCounts[shape] += count;
} else {
blockCounts[shape] = count;
}
totalArea += count * (BLOCK_SIZES[shape] || 0);
}
// 验证面积
const gridArea = width * height;
if (totalArea !== gridArea) {
throw new Error(`面积不匹配: 网格面积=${gridArea}, 方块总面积=${totalArea}`);
}
return {
width,
height,
blockCounts,
totalBlocks: Object.values(blockCounts).reduce((a, b) => a + b, 0)
};
}
/**
* 求解方块覆盖问题
* @param {number} gridWidth - 网格宽度
* @param {number} gridHeight - 网格高度
* @param {Object} blockCounts - 方块数量映射
* @returns {Array<Array<string>>|null} - 解决方案矩阵,无解返回null
*/
function solveBlockPuzzle(gridWidth, gridHeight, blockCounts) {
// 生成列定义
const columns = [];
// 单元格约束列
for (let y = 0; y < gridHeight; y++) {
for (let x = 0; x < gridWidth; x++) {
columns.push(`${y},${x}`);
}
}
// 方块实例约束列
for (let block in blockCounts) {
for (let i = 0; i < blockCounts[block]; i++) {
columns.push(`${block}_${i}`);
}
}
// 创建舞蹈链求解器
const dlx = new DancingLinksSolver(columns);
// 收集所有可能的放置方式
const allPlacements = [];
for (let block in blockCounts) {
for (let count = 0; count < blockCounts[block]; count++) {
// 原始方向
BLOCKS[block].forEach(shape => {
const placements = getAllPlacements(shape, gridWidth, gridHeight);
placements.forEach(cells => {
allPlacements.push({
block,
cells,
count,
shape
});
});
});
// 如果需要翻转,添加翻转后的方向
if (['D', 'L', 'S', 'T', 'U'].includes(block)) {
BLOCKS[block].forEach(shape => {
const mirrored = reflectShape(shape);
const placements = getAllPlacements(mirrored, gridWidth, gridHeight);
placements.forEach(cells => {
allPlacements.push({
block,
cells,
count,
shape: mirrored
});
});
});
}
}
}
// 打乱放置顺序以获得随机解
shuffle(allPlacements);
// 构建舞蹈链数据结构
allPlacements.forEach(placement => {
const nodesInRow = [];
// 添加单元格约束节点
placement.cells.forEach(cell => {
const colNode = dlx.columns[cell];
if (!colNode) return;
const newNode = new DataNode(colNode);
nodesInRow.push(newNode);
newNode.up = colNode.up;
newNode.down = colNode;
colNode.up.down = newNode;
colNode.up = newNode;
colNode.size++;
});
// 添加方块实例约束节点
const blockInstance = `${placement.block}_${placement.count}`;
const blockColNode = dlx.columns[blockInstance];
if (blockColNode) {
const newNode = new DataNode(blockColNode);
nodesInRow.push(newNode);
newNode.up = blockColNode.up;
newNode.down = blockColNode;
blockColNode.up.down = newNode;
blockColNode.up = newNode;
blockColNode.size++;
}
// 连接行内的节点
nodesInRow.forEach((node, i) => {
node.left = nodesInRow[i - 1] || nodesInRow[nodesInRow.length - 1];
node.right = nodesInRow[i + 1] || nodesInRow[0];
});
});
// 搜索解
const solutions = [];
dlx.search(0, [], solutions);
if (solutions.length === 0) {
return null;
}
// 将第一个解转换为矩阵形式
const solutionMatrix = Array(gridHeight).fill().map(() => Array(gridWidth).fill('.'));
const solution = solutions[0];
solution.forEach(rowNode => {
// 找到这个方块对应的单元格
const blockCells = [];
let node = rowNode;
do {
if (node.column && /\d+,\d+/.test(node.column.name)) {
blockCells.push(node.column.name);
}
node = node.right;
} while (node !== rowNode);
// 找到方块类型
let blockType = null;
node = rowNode;
do {
if (node.column && /^[A-Z]_\d+$/.test(node.column.name)) {
blockType = node.column.name.split('_')[0];
break;
}
node = node.right;
} while (node !== rowNode);
// 在矩阵中标记方块
if (blockType && blockCells.length > 0) {
blockCells.forEach(cell => {
const [y, x] = cell.split(',').map(Number);
if (y >= 0 && y < gridHeight && x >= 0 && x < gridWidth) {
solutionMatrix[y][x] = blockType;
}
});
}
});
return solutionMatrix;
}
/**
* 格式化输出矩阵
* @param {Array<Array<string>>} matrix - 解决方案矩阵
* @returns {string} - 格式化后的字符串
*/
function formatMatrix(matrix) {
return matrix.map(row => row.join(' ')).join('\n');
}
// ============================
// 主函数(命令行接口)
// ============================
/**
* 命令行主函数
*/
function main() {
// 判断是否在Node.js环境中
if (typeof process !== 'undefined' && process.argv) {
// 从命令行参数读取输入
if (process.argv.length < 3) {
console.log('用法: node block_solver_core.js "width,height,[shape,count...]"');
console.log('示例: node block_solver_core.js "4,4,[A,4,B,6]"');
return;
}
const inputStr = process.argv[2];
try {
// 解析输入
const { width, height, blockCounts } = parseInput(inputStr);
console.log(`求解 ${width}x${height} 网格的方块覆盖问题...`);
console.log(`方块配置: ${JSON.stringify(blockCounts)}`);
// 求解
const startTime = performance.now();
const solution = solveBlockPuzzle(width, height, blockCounts);
const endTime = performance.now();
console.log(`求解时间: ${(endTime - startTime).toFixed(2)}ms`);
// 输出结果
if (solution) {
console.log('\n找到解决方案:');
console.log(formatMatrix(solution));
} else {
console.log('\n无解');
}
} catch (error) {
console.error('错误:', error.message);
}
} else {
// 浏览器环境 - 提供测试接口
console.log('方块覆盖求解器核心模块已加载');
console.log('在Node.js环境中使用: node block_solver_core.js "width,height,[shape,count...]"');
// 测试用例
const testCases = [
"4,4,[A,4,B,6]", // 4x4网格,4个A方块,6个B方块
"3,3,[A,9]", // 3x3网格,9个A方块
"2,4,[B,4]", // 2x4网格,4个B方块
"4,2,[I,2]", // 4x2网格,2个I方块
"4,5,[A,5,B,5,C,5]" // 4x5网格,各5个A,B,C方块
];
console.log('\n测试用例:');
testCases.forEach(testCase => {
try {
const { width, height, blockCounts } = parseInput(testCase);
const solution = solveBlockPuzzle(width, height, blockCounts);
console.log(`\n输入: ${testCase}`);
if (solution) {
console.log('有解');
} else {
console.log('无解');
}
} catch (error) {
console.log(`\n输入: ${testCase}`);
console.log(`错误: ${error.message}`);
}
});
}
}
// 如果直接运行此文件,执行主函数
if (typeof module !== 'undefined' && module.exports) {
module.exports = {
parseInput,
solveBlockPuzzle,
formatMatrix,
BLOCKS,
BLOCK_SIZES
};
// 如果通过命令行调用,执行主函数
if (require.main === module) {
main();
}
}
// 浏览器环境直接执行
if (typeof window !== 'undefined') {
main();
}
用node.js执行结果如下:
node-v24.4.1-linux-x64/bin/node block_solver_core.js "4,4,[A,4,B,6]"
求解 4x4 网格的方块覆盖问题...
方块配置: {"A":4,"B":6}
求解时间: 22.74ms
找到解决方案:
B B B B
B B B B
B B B A
A A B A
node-v24.4.1-linux-x64/bin/node block_solver_core.js "3,3,[A,9]"
求解 3x3 网格的方块覆盖问题...
方块配置: {"A":9}
求解时间: 2.56ms
找到解决方案:
A A A
A A A
A A A
node-v24.4.1-linux-x64/bin/node block_solver_core.js "5,5,[O,1,X,1,U,2,L,2]"
错误: 面积不匹配: 网格面积=25, 方块总面积=27
也可以自定义形状,实现任意形状的覆盖。


被折叠的 条评论
为什么被折叠?



