UNPKG

@amanwebdev/sudoku-generator

Version:
361 lines (360 loc) 12 kB
"use strict"; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; Object.defineProperty(exports, "__esModule", { value: true }); exports.generateSudoku = generateSudoku; exports.toPDF = toPDF; exports.toImage = toImage; const jspdf_1 = require("jspdf"); const canvas_1 = require("canvas"); function generateSudoku(size, difficulty) { const grid = createEmptyGrid(size); const solution = createEmptyGrid(size); fillGrid(grid, size); copyGrid(grid, solution); removeNumbers(grid, difficulty, size); return { size, grid, solution, difficulty, }; } function createEmptyGrid(size) { return Array(size) .fill(0) .map(() => Array(size).fill(0)); } function copyGrid(from, to) { for (let i = 0; i < from.length; i++) { for (let j = 0; j < from[i].length; j++) { to[i][j] = from[i][j]; } } } function fillGrid(grid, size) { const emptyCell = findEmptyCell(grid, size); if (!emptyCell) { return true; } const [row, col] = emptyCell; const numbers = shuffle(getPossibleNumbers(size)); for (const num of numbers) { if (isValid(grid, row, col, num, size)) { grid[row][col] = num; if (fillGrid(grid, size)) { return true; } grid[row][col] = 0; } } return false; } function findEmptyCell(grid, size) { for (let i = 0; i < size; i++) { for (let j = 0; j < size; j++) { if (grid[i][j] === 0) { return [i, j]; } } } return null; } function getPossibleNumbers(size) { const numbers = []; for (let i = 1; i <= size; i++) { numbers.push(i); } return numbers; } 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]]; } return array; } function isValid(grid, row, col, num, size) { return (!isInRow(grid, row, num, size) && !isInCol(grid, col, num, size) && !isInBox(grid, row, col, num, size)); } function isInRow(grid, row, num, size) { for (let i = 0; i < size; i++) { if (grid[row][i] === num) { return true; } } return false; } function isInCol(grid, col, num, size) { for (let i = 0; i < size; i++) { if (grid[i][col] === num) { return true; } } return false; } function isInBox(grid, row, col, num, size) { let boxSizeRow; let boxSizeCol; if (size === 4) { boxSizeRow = 2; boxSizeCol = 2; } else if (size === 6) { boxSizeRow = 2; boxSizeCol = 3; } else { boxSizeRow = 3; boxSizeCol = 3; } const startRow = row - (row % boxSizeRow); const startCol = col - (col % boxSizeCol); for (let i = 0; i < boxSizeRow; i++) { for (let j = 0; j < boxSizeCol; j++) { if (grid[startRow + i][startCol + j] === num) { return true; } } } return false; } function removeNumbers(grid, difficulty, size) { const attempts = getAttempts(difficulty, size); for (let i = 0; i < attempts; i++) { let row = Math.floor(Math.random() * size); let col = Math.floor(Math.random() * size); while (grid[row][col] === 0) { row = Math.floor(Math.random() * size); col = Math.floor(Math.random() * size); } const backup = grid[row][col]; grid[row][col] = 0; const gridCopy = createEmptyGrid(size); copyGrid(grid, gridCopy); if (!hasUniqueSolution(gridCopy, size)) { grid[row][col] = backup; } } } function getAttempts(difficulty, size) { const totalCells = size * size; let percentage = 0; switch (difficulty) { case "easy": percentage = 0.4; break; case "medium": percentage = 0.55; break; case "hard": percentage = 0.7; break; } return Math.floor(totalCells * percentage); } function hasUniqueSolution(grid, size) { let count = 0; solve(grid, size, () => { count++; }); return count === 1; } function solve(grid, size, onSolution) { const emptyCell = findEmptyCell(grid, size); if (!emptyCell) { onSolution(); return; } const [row, col] = emptyCell; const numbers = getPossibleNumbers(size); for (const num of numbers) { if (isValid(grid, row, col, num, size)) { grid[row][col] = num; solve(grid, size, onSolution); grid[row][col] = 0; } } } function getThemeColors(theme) { if (theme === "light" || !theme) { return { background: "#ffffff", gridColor: "#000000", textColor: "#000000", boxLineColor: "#000000", }; } else if (theme === "dark") { return { background: "#2d2d2d", gridColor: "#ffffff", textColor: "#ffffff", boxLineColor: "#ffffff", }; } else { return theme; } } function toPDF(grid_1) { return __awaiter(this, arguments, void 0, function* (grid, options = {}, solution) { const { theme = "light", title = "Sudoku Puzzle", author = "Sudoku Generator", subject = "Sudoku Puzzle", keywords = "sudoku, puzzle, game", showSolution = false, } = options; const colors = getThemeColors(theme); const size = grid.length; const doc = new jspdf_1.jsPDF({ orientation: "portrait", unit: "mm", format: "a4", }); doc.setProperties({ title, author, subject, keywords, }); if (colors.background !== "#ffffff") { doc.setFillColor(colors.background); doc.rect(0, 0, 210, 297, "F"); } doc.setTextColor(colors.textColor); doc.setFont("helvetica", "bold"); doc.setFontSize(24); doc.text(title, 105, 30, { align: "center" }); const cellSize = size === 9 ? 20 : size === 6 ? 25 : 30; const totalGridSize = size * cellSize; const startX = (210 - totalGridSize) / 2; const startY = 60; doc.setDrawColor(colors.gridColor); for (let i = 0; i <= size; i++) { const isBoxLine = getBoxLinePositions(size).includes(i); const lineWidth = isBoxLine ? 2.268 : 0.85; doc.setLineWidth(lineWidth); const x = startX + i * cellSize; doc.line(x, startY, x, startY + totalGridSize); const y = startY + i * cellSize; doc.line(startX, y, startX + totalGridSize, y); } doc.setTextColor(colors.textColor); doc.setFont("helvetica", "bold"); doc.setFontSize(size === 9 ? 14 : size === 6 ? 16 : 20); for (let row = 0; row < size; row++) { for (let col = 0; col < size; col++) { const puzzleValue = grid[row][col]; const solutionValue = solution ? solution[row][col] : 0; let displayValue = 0; let isGivenDigit = false; if (showSolution && solution) { displayValue = solutionValue; isGivenDigit = puzzleValue !== 0; } else { displayValue = puzzleValue; isGivenDigit = true; } if (displayValue !== 0) { const x = startX + col * cellSize + cellSize / 2; const y = startY + row * cellSize + cellSize / 2 + (size === 9 ? 3 : size === 6 ? 4 : 5); if (showSolution && !isGivenDigit) { doc.setFont("helvetica", "normal"); doc.setTextColor(colors.textColor === "#000000" ? "#666666" : "#cccccc"); } else { doc.setFont("helvetica", "bold"); doc.setTextColor(colors.textColor); } doc.text(displayValue.toString(), x, y, { align: "center" }); } } } return new Uint8Array(doc.output("arraybuffer")); }); } function getBoxLinePositions(size) { if (size === 4) { return [0, 2, 4]; } else if (size === 6) { return [0, 2, 4, 6]; } else { return [0, 3, 6, 9]; } } function toImage(grid, options = {}, solution) { const { theme = "light", cellSize = 60, showSolution = false, format = "png", quality = 0.95, } = options; const colors = getThemeColors(theme); const size = grid.length; const totalGridSize = size * cellSize; const padding = cellSize; const canvasWidth = totalGridSize + padding * 2; const canvasHeight = totalGridSize + padding * 2; const canvas = (0, canvas_1.createCanvas)(canvasWidth, canvasHeight); const ctx = canvas.getContext("2d"); ctx.fillStyle = colors.background; ctx.fillRect(0, 0, canvasWidth, canvasHeight); ctx.strokeStyle = colors.gridColor; ctx.lineWidth = 1; for (let i = 0; i <= size; i++) { const isBoxLine = getBoxLinePositions(size).includes(i); ctx.lineWidth = isBoxLine ? 3 : 1; const x = padding + i * cellSize; ctx.beginPath(); ctx.moveTo(x, padding); ctx.lineTo(x, padding + totalGridSize); ctx.stroke(); const y = padding + i * cellSize; ctx.beginPath(); ctx.moveTo(padding, y); ctx.lineTo(padding + totalGridSize, y); ctx.stroke(); } const fontSize = cellSize * 0.5; ctx.font = `bold ${fontSize}px Arial, sans-serif`; ctx.textAlign = "center"; ctx.textBaseline = "middle"; for (let row = 0; row < size; row++) { for (let col = 0; col < size; col++) { const puzzleValue = grid[row][col]; const solutionValue = solution ? solution[row][col] : 0; let displayValue = 0; let isGivenDigit = false; if (showSolution && solution) { displayValue = solutionValue; isGivenDigit = puzzleValue !== 0; } else { displayValue = puzzleValue; isGivenDigit = true; } if (displayValue !== 0) { const x = padding + col * cellSize + cellSize / 2; const y = padding + row * cellSize + cellSize / 2; if (showSolution && !isGivenDigit) { ctx.fillStyle = colors.textColor === "#000000" ? "#666666" : "#cccccc"; ctx.font = `${fontSize}px Arial, sans-serif`; } else { ctx.fillStyle = colors.textColor; ctx.font = `bold ${fontSize}px Arial, sans-serif`; } ctx.fillText(displayValue.toString(), x, y); } } } if (format === "jpeg") { return canvas.toBuffer("image/jpeg", { quality }); } else { return canvas.toBuffer("image/png"); } }