-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathindex.ts
130 lines (104 loc) · 4.69 KB
/
index.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
import Logic = require('logic-solver')
import assert = require('assert')
class SudokuSolver {
private addBlockConstraints(solver, size: number) {
const blockSize = Math.sqrt(size)
for (let blockRow = 0; blockRow < blockSize; blockRow++) {
for (let blockCol = 0; blockCol < blockSize; blockCol++) {
for (let value = 1; value <= size; value++) {
let varsForBlock: string[] = []
for (let rowDelta = 0; rowDelta < blockSize; rowDelta++) {
for (let colDelta = 0; colDelta < blockSize; colDelta++) {
const row = blockRow * blockSize + rowDelta
const col = blockCol * blockSize + colDelta
varsForBlock.push(`R${row}C${col}#${value}`)
}
}
solver.require(Logic.exactlyOne(...varsForBlock))
}
}
}
}
private addSudokuConstraints(solver, puzzle: string, characters: string, emptyChar: string) {
let size = Math.sqrt(puzzle.length);
let grid = [];
for (let i = 0; i < size; i++) {
grid[i] = puzzle.slice(i * size, (i + 1) * size).split('')
}
this.printGrid(grid)
for (let row = 0; row < size; row++) {
for (let col = 0; col < size; col++) {
if (grid[row][col] !== emptyChar) {
const value = characters.indexOf(grid[row][col]) + 1
const varName = `R${row}C${col}#${value}`
solver.require(Logic.exactlyOne(varName))
} else {
solver.require(Logic.or(...characters.split('').map((char, index) => {
const value = index + 1
const varName = `R${row}C${col}#${value}`
return varName
})))
}
}
}
for (let row = 0; row < size; row++) {
for (let value = 1; value <= size; value++) {
let varsForRow: string[] = []
for (let col = 0; col < size; col++) {
varsForRow.push(`R${row}C${col}#${value}`)
}
solver.require(Logic.exactlyOne(...varsForRow))
}
}
for (let col = 0; col < size; col++) {
for (let value = 1; value <= size; value++) {
let varsForCol: string[] = []
for (let row = 0; row < size; row++) {
varsForCol.push(`R${row}C${col}#${value}`)
}
solver.require(Logic.exactlyOne(...varsForCol))
}
}
this.addBlockConstraints(solver, size)
}
public solveSudoku(puzzle: string, characters: string, emptyChar: string): string | null {
const solver = new Logic.Solver()
this.addSudokuConstraints(solver, puzzle, characters, emptyChar)
const solution = solver.solve()
if (solution) {
const size = Math.sqrt(puzzle.length);
const solvedGrid: string[][] = new Array(size).fill(null).map(() => new Array(size).fill(emptyChar))
solution.getTrueVars().forEach(varName => {
const matches = /R(\d+)C(\d+)#(\d+)/.exec(varName)
if (matches) {
const row = parseInt(matches[1], 10)
const col = parseInt(matches[2], 10)
const valueIndex = parseInt(matches[3], 10) - 1
const value = characters[valueIndex]
solvedGrid[row][col] = value
}
})
this.printGrid(solvedGrid)
return solvedGrid.map(row => row.join('')).join('')
} else {
return null
}
}
private printGrid(grid: string[][]) {
// grid.forEach(row => console.log(row.join(' ')))
}
}
const main = (): string | null => {
const [puzzle, characters, emptyChar] = process.argv.slice(2)
if (!puzzle || !characters || !emptyChar) {
console.error('Usage: node index.js <Puzzle> <Valid characters> <Empty cell character>')
process.exit(1)
}
const size = Math.sqrt(puzzle.length)
assert.ok(size === characters.length, `Number of valid character does not match the size of the puzzle, Puzzle: ${size}, Characters: ${characters.length}`)
const sudokuSolver = new SudokuSolver()
const solution = sudokuSolver.solveSudoku(puzzle, characters, emptyChar)
assert.ok(solution?.length === puzzle.length, 'Solution length does not match the length of the puzzle')
return solution
}
console.log(main())