Created
June 9, 2020 05:52
-
-
Save SiestaMadokaist/c53524ab1c8414652f331039d15fa273 to your computer and use it in GitHub Desktop.
sudoku generator
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class MySet<T> extends Set<T> { | |
static of<T>(...xs: T[]): MySet<T> { | |
return new this<T>(xs); | |
} | |
intersect(other: MySet<T>): MySet<T> { | |
const result = new MySet<T>(); | |
for (const entry of this.values()) { | |
if (other.has(entry)) { result.add(entry); } | |
} | |
return result; | |
} | |
} | |
const createEntries = (n: number) => { | |
return [ ...new Array(n * n)].map((_, i) => i + 1); | |
}; | |
abstract class Entries { | |
#entries: MySet<number>; | |
abstract type: string; | |
constructor(size: number, private name: string = '') { this.#entries = MySet.of(...createEntries(size)); } | |
entries(): MySet<number> { | |
return this.#entries; | |
} | |
delete(n: number): void { | |
this.#entries.delete(n); | |
} | |
print(): void { | |
console.log(`${this.name}`); | |
console.log(Array.from(this.#entries).join(', ')); | |
} | |
} | |
class Row extends Entries { type: 'row' = 'row'; } | |
class Column extends Entries { type: 'column' = 'column'; } | |
class Box extends Entries { type: 'box' = 'box'; } | |
class SudokuBoard { | |
private _board: number[][]; | |
private _boxes: Box[]; | |
private _columns: Column[]; | |
private _rows: Row[]; | |
static create(size: number): SudokuBoard { | |
const board = new this(size); | |
board.initialize(); | |
return board; | |
} | |
private constructor(private size: number) { | |
const size2 = size * size; | |
const generator = [ ...new Array(size2) ]; | |
this._board = generator.map(() => createEntries(size).map(() => 0)); | |
this._boxes = generator.map((_, i) => new Box(size, `box#${i}`)); | |
this._columns = generator.map((_, i) => new Column(size, `column#${i}`)); | |
this._rows = generator.map((_, i) => new Row(size, `row#${i}`)); | |
} | |
board(): number[][] { | |
return this._board; | |
} | |
private size2(): number { return this.size * this.size; } | |
private candidate(x: number, y: number): MySet<number> { | |
const box = this.getBox(x, y); | |
const column = this.getColumn(x, y) | |
const row = this.getRow(x, y); | |
const intersect = box.entries().intersect(column.entries()).intersect(row.entries()); | |
return intersect; | |
} | |
deleteCandidate(index: {x: number, y: number}, value: number): void { | |
const box = this.getBox(index.x, index.y); | |
const column = this.getColumn(index.x, index.y) | |
const row = this.getRow(index.x, index.y); | |
box.delete(value); | |
column.delete(value); | |
row.delete(value); | |
} | |
private setValue(index: { x: number, y: number}, value: number): void { | |
this.deleteCandidate(index, value); | |
this._board[index.y][index.x] = value; | |
} | |
private setRandom(index: { x: number; y: number }): void { | |
const intersect = this.candidate(index.x, index.y); | |
const available = Array.from(intersect); | |
if (available.length <= 0) { throw new Error('');} | |
const elementIndex = Math.floor(Math.random() * available.length); | |
const value = available[elementIndex]; | |
this.setValue(index, value); | |
} | |
private initialize(): void { | |
for (let p = 0; p < this.size2(); p++) { | |
const y = 0; | |
this.setRandom({ x: p, y }); | |
} | |
const indexes: Set<{ x: number, y: number }> = new Set(); | |
for (let y = 1; y < this.size2(); y++) { | |
for (let x = 0; x < this.size2(); x++) { | |
indexes.add({ x, y }) | |
} | |
} | |
const size = indexes.size; | |
for (let i = 0; i < size; i++) { | |
const candidates = Array.from(indexes).map((index) => ({ | |
index, | |
candidate: this.candidate(index.x, index.y) | |
})).sort((a, b) => b.candidate.size - a.candidate.size); | |
const least = candidates.pop(); | |
if (!least) { throw new Error(''); } | |
indexes.delete(least.index); | |
this.setRandom(least.index); | |
} | |
} | |
print(): void { | |
for (let y = 0; y < this.size2(); y++) { | |
const buffer: string[] = []; | |
for (let x = 0; x < this.size2(); x++) { | |
const value = this._board[y][x]; | |
if (x % this.size === 0) { buffer.push('|')} | |
// buffer.push(`${value}`); | |
if (value > 0) { | |
const text = `${value}`;//.padStart(10).padEnd(15); | |
buffer.push(text); | |
} else { | |
const candidate = this.candidate(x, y); | |
const content = `(${Array.from(candidate).join(',')})`; | |
const text = content;//.padStart(15 - content.length / 2).padEnd(15); | |
buffer.push(text); | |
} | |
if (x === this.size2() - 1) { | |
buffer.push(`|`); | |
continue; | |
} | |
} | |
const l = 2 * this.size2() + 1 + (2 * this.size); | |
const separator = [ ...new Array(l)].map(() => '-').join(''); | |
if (y % this.size === 0) { console.log(separator); } | |
console.log(buffer.join(' ')); | |
if (y === this.size2() - 1) { console.log(separator); } | |
} | |
} | |
private getBox(x: number, y: number): Box { | |
const xn = Math.floor(x / this.size); | |
const yn = Math.floor(y / this.size); | |
const index = (yn * this.size) + xn; | |
return this._boxes[index]; | |
} | |
private getColumn(x: number, y: number): Column { | |
return this._columns[x]; | |
} | |
private getRow(x: number, y: number): Row { | |
return this._rows[y]; | |
} | |
} | |
function main(): void { | |
const tryCount = 100; | |
let successfull = 0; | |
// const board = SudokuBoard.create(3); | |
// board.print(); | |
for (let i = 0; i < tryCount; i++) { | |
let board: SudokuBoard | |
try { | |
board = SudokuBoard.create(3); | |
successfull++; | |
console.log(`success #${successfull} #${i}/${tryCount}`); | |
board.print(); | |
} catch (error) { } | |
} | |
} | |
main(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment