From 27918e7a356901fc339772e20750af290576d5ae Mon Sep 17 00:00:00 2001 From: LordBaryhobal Date: Tue, 30 Apr 2024 14:28:34 +0200 Subject: [PATCH] added SudokuSolver --- .../sudoku/GraphicsDisplay.scala | 61 ++++++++++ src/magic_squares/sudoku/SudokuSolver.scala | 109 ++++++++++++++++++ 2 files changed, 170 insertions(+) create mode 100644 src/magic_squares/sudoku/GraphicsDisplay.scala create mode 100644 src/magic_squares/sudoku/SudokuSolver.scala diff --git a/src/magic_squares/sudoku/GraphicsDisplay.scala b/src/magic_squares/sudoku/GraphicsDisplay.scala new file mode 100644 index 0000000..56edda9 --- /dev/null +++ b/src/magic_squares/sudoku/GraphicsDisplay.scala @@ -0,0 +1,61 @@ +package magic_squares.sudoku + +import hevs.graphics.FunGraphics +import magic_squares.{Displayable, Grid} + +import java.awt.{Color, Font} +import javax.swing.SwingConstants + +trait GraphicsDisplay extends Displayable { + private var _win: Option[FunGraphics] = None + private val WIDTH: Int = 400 + private val HEIGHT: Int = 400 + private val MARGIN: Int = 20 + private val FONT: Font = new Font("Arial", Font.PLAIN, 24) + override def display(g: Grid): Unit = { + if (_win.isEmpty) { + _win = Some(new FunGraphics(WIDTH, HEIGHT)) + } + val win: FunGraphics = _win.get + win.clear() + val size: Int = SudokuSolver.SIZE + val squareSize: Int = SudokuSolver.SQUARE_SIZE + + val w: Int = WIDTH - 2 * MARGIN + val h: Int = HEIGHT - 2 * MARGIN + val gridSize: Int = math.min(w, h) + val cellSize: Int = gridSize / size + val ox: Int = (WIDTH - gridSize) / 2 + val oy: Int = (HEIGHT - gridSize) / 2 + + win.setPenWidth(2) + win.drawRect(ox, oy, gridSize, gridSize) + for (i: Int <- 1 until size / squareSize) { + win.drawLine(ox + cellSize * squareSize * i, oy, ox + cellSize * squareSize * i, oy + gridSize) + win.drawLine(ox, oy + cellSize * squareSize * i, ox + gridSize, oy + cellSize * squareSize * i) + } + win.setPenWidth(1) + for (i: Int <- 1 until size) { + win.drawLine(ox + cellSize * i, oy, ox + cellSize * i, oy + gridSize) + win.drawLine(ox, oy + cellSize * i, ox + gridSize, oy + cellSize * i) + } + + for (y: Int <- 0 until size) { + for (x: Int <- 0 until size) { + val v: Int = g(y)(x) + if (v != 0) { + win.drawString( + (ox + cellSize * (x + 0.5)).toInt, + (oy + cellSize * (y + 0.5)).toInt, + v.toString, + FONT, + Color.BLACK, + SwingConstants.CENTER, + SwingConstants.CENTER + ) + } + } + } + win.syncGameLogic(60) + } +} diff --git a/src/magic_squares/sudoku/SudokuSolver.scala b/src/magic_squares/sudoku/SudokuSolver.scala new file mode 100644 index 0000000..9afabf4 --- /dev/null +++ b/src/magic_squares/sudoku/SudokuSolver.scala @@ -0,0 +1,109 @@ +package magic_squares.sudoku + +import magic_squares.{Displayable, Grid} + +import scala.collection.mutable.ArrayBuffer + +abstract class SudokuSolver(initialGrid: Grid) extends Displayable { + protected var _initial: Grid = initialGrid + protected var grid: Grid = copy(_initial) + protected val SIZE: Int = SudokuSolver.SIZE + protected val SQUARE_SIZE: Int = SudokuSolver.SQUARE_SIZE + protected val DEBUG: Boolean = false + protected val NUMBERS: Array[Int] = Array.range(1, SIZE + 1) + + protected def print(grid: Grid): Unit = { + for (y: Int <- 0 until SIZE) { + println(grid(y).mkString(",")) + } + } + + protected def copy(grid: Grid): Grid = { + return grid.map(_.clone()) + } + + def solve(): Unit = { + val solved: Boolean = _solve() + if (solved) { + display(grid) + } else { + println("No solution") + } + } + + private def _solve(): Boolean = { + display(grid) + + if (DEBUG) print(grid) + + val (x: Int, y: Int) = getNextEmpty() + if (x == -1) { + if (DEBUG) println(" Found solution") + return true + } + val numbers: Array[Int] = getPossibleNumbers(x, y) + if (numbers.isEmpty) { + return false + } + + if (DEBUG) println(s" Values to test: " + numbers.mkString("[", ", ", "]")) + + for (n: Int <- numbers) { + if (DEBUG) println(s" Testing $n") + grid(y)(x) = n + if (_solve()) { + if (DEBUG) println(" Found solution, collapsing call stack") + return true + } + } + grid(y)(x) = 0 + + if (DEBUG) println(s" No solution for this configuration") + return false + } + + private def getNextEmpty(): (Int, Int) = { + for (y: Int <- 0 until SIZE) { + for (x: Int <- 0 until SIZE) { + if (grid(y)(x) == 0) { + return (x, y) + } + } + } + return (-1, -1) + } + + private def getPossibleNumbers(x: Int, y: Int): Array[Int] = { + val inGrid: ArrayBuffer[Int] = grid(y).concat(grid.map(_(x))).to(ArrayBuffer) + val sx: Int = (x / 3) * 3 + val sy: Int = (y / 3) * 3 + for (dy: Int <- 0 until SQUARE_SIZE) { + for (dx: Int <- 0 until SQUARE_SIZE) { + inGrid.addOne(grid(sy + dy)(sx + dx)) + } + } + return NUMBERS.diff(inGrid.distinct) + } +} + +object SudokuSolver { + val SIZE: Int = 9 + val SQUARE_SIZE: Int = SIZE / 3 + + def main(args: Array[String]): Unit = { + val solver1: SudokuSolver = new SudokuSolver(Array( + Array(5,3,0, 0,7,0, 0,0,0), + Array(6,0,0, 1,9,5, 0,0,0), + Array(0,9,8, 0,0,0, 0,6,0), + + Array(8,0,0, 0,6,0, 0,0,3), + Array(4,0,0, 8,0,3, 0,0,1), + Array(7,0,0, 0,2,0, 0,0,6), + + Array(0,6,0, 0,0,0, 2,8,0), + Array(0,0,0, 4,1,9, 0,0,5), + Array(0,0,0, 0,8,0, 0,7,9), + )) with GraphicsDisplay + solver1.solve() + } +} \ No newline at end of file