Der Quelltext von der Webseite ist übrigens nicht gut. Der hält sich nicht an die üblichen Konventionen wie Einrückung mit vier Leerzeichen pro Ebene und das Namen in Python klein_mit_unterstrichen geschrieben werden. Ausnahmen sind Konstanten (KOMPLETT_GROSS) und Klassen (PascalCase).
Grunddatentypen haben nichts in Namen verloren. Den Typen ändert man gar nicht so selten mal während der Programmentwicklung und dann muss man überall im Programm die betroffenen Namen ändern, oder man hat falsche, irreführende Namen im Quelltext.
Der Namenszusatz `my` macht keinen Sinn wenn es den gleichen Namen nicht auch mit `our` oder `their` gibt, gegen den sich das `my` irgendwie abgrenzen würde.
Auf Modulebene sollte nur Code stehen der Konstanten, Funktionen, und Klassen definiert. Das Hauptprogramm steht üblicherweise in einer Funktion die `main()` heisst. ``global`` und globale Variablen verwendet man nicht. Alles was eine Funktion/Methode ausser Konstanten benötigt, bekommt sie als Argument(e) übergeben. In `fillGrid()` ist ``global`` sogar überflüssig, weil dort überhaupt nicht auf `counter` zugegriffen wird.
Kommentare vor Funktionen die beschreiben was die Funktion tut, sollten keine Kommentare sondern Docstrings sein.
Das erstellen des leeren Sodukofelds durch neun mal hinschreiben des gleichen `append()` mit literalen Listen mit jeweis neun Nullen ist nicht idiomatisch für Python. Mal abgesehen davon, dass man die gesamte verschachtelte Liste als literal hätte schreiben können, würde man Code schreiben der diese Datenstruktur erstellt, statt da mit viel Tipparbeit und/oder kopieren und einfügen zu arbeiten.
Statt ``not a in b`` würde man ``a not in b`` schreiben.
Statt beim Test ob der Wert schon in der Spalte vorkommt, jeden Index von 0 bis 8 manuell hin zu schreiben, würde man das in einer Schleife machen.
Für `square` ist zu viel Code verwendet worden. Statt der vielen ``if``-Bedingungen könnte man die entsprechenden Indizes auch einfach ausrechnen.
`square` wird an eine leere Liste gebunden die niemals irgendwo verwendet wird.
Es ist extrem verwirrend, dass in der Schleife mit der Laufvariablen `i` „list comprehensions“ stehen die ebenfalls `i` als Laufvariable verwenden. Auch wenn das ein eigener Namensraum ist, mag das dem menschlichen Leser nicht immer klar sein.
Die `fillGrid()`-Funktion gibt entweder `True` oder `None` zurück — das sollte `True` oder `False` sein.
`checkGrid()` lässt sich mit Comprehensions ohne Indexzugriffe und `any()` oder `all()` mit einem Ausdruck beschreiben.
`drawGrid()` braucht `myPen` als Argument statt das einfach so magisch aus der Umgebung zu verwenden.
Namen sollten keine kryptischen Abkürzungen enthalten, oder gar nur daraus bestehen. `intDim`?
Funktionen und Methoden werden üblicherweise nach der Tätigkeit benannt die sie durchführen um sie von eher passiven Werten unterscheiden zu können. `text()` ist keine Tätigkeit, das ist also kein guter Funktionsname.
`counter` wird an eine 1 gebunden die nirgends verwendet wird.
Die Auswahl einer zufälligen, nicht-leeren Zelle wiederholt Code vor und in der Schleife. Da würde man besser eine ”Endlosschleife” schreiben, die bei entsprechender Bedingung dann per ``break`` verlassen wird.
`copyGrid` klingt wie eine Funktion, das sollte besser `grid_copy` heissen, beziehungsweise muss das auch gar nicht wirklich an einen Namen gebunden werden, denn das kann man mit einem Ausdruck erzeugen, statt da Listen und zusätzliche Namen zu verwenden.
`solveGrid()` sollte die Anzahl der Lösungen als Ergebnis liefern und keine globale Variable verändern.
In `solveGrid()` und `fillGrid()` wiederholt sich viel Code, das sollte nicht sein. Der gehört in eine oder mehrere eigene Funktionen.
Überarbeitete Version:
Code: Alles auswählen
#!/usr/bin/env python3
import turtle
from random import randint, shuffle
from time import sleep
CELL_SIZE = 35
TOP_LEFT_X = -150
TOP_LEFT_Y = 150
def check_grid(grid):
"""
A function to check if the grid is full.
"""
return all(all(value != 0 for value in row) for row in grid)
def is_value_in_row(grid, value, row_index):
return value in grid[row_index]
def is_value_in_column(grid, value, column_index):
return any(value == row[column_index] for row in grid)
def is_value_in_square(grid, value, row_index, column_index):
y = row_index // 3 * 3
x = column_index // 3 * 3
return any(value in row[x : x + 3] for row in grid[y : y + 3])
def is_valid_value(grid, value, row_index, column_index):
return not (
is_value_in_row(grid, value, row_index)
or is_value_in_column(grid, value, column_index)
or is_value_in_square(grid, value, row_index, column_index)
)
def fill_grid(grid, numbers):
"""
A backtracking/recursive function to check all possible combinations of
numbers until a solution is found.
"""
for row_index, row in enumerate(grid):
for column_index, old_value in enumerate(row):
if old_value == 0:
shuffle(numbers)
for value in numbers:
if is_valid_value(grid, value, row_index, column_index):
row[column_index] = value
if check_grid(grid):
return True
if fill_grid(grid, numbers):
return True
row[column_index] = 0
return False
assert False
def solve_grid(grid, counter=0):
"""
A backtracking/recursive function to check all possible combinations of
numbers until a solution is found.
"""
for row_index, row in enumerate(grid):
for column_index, old_value in enumerate(row):
if old_value == 0:
for value in range(1, 10):
if is_valid_value(grid, value, row_index, column_index):
row[column_index] = value
if check_grid(grid):
counter += 1
break
counter = solve_grid(grid, counter)
row[column_index] = 0
return counter
assert False
def draw_text(pen, message, x, y, size):
pen.penup()
pen.goto(x, y)
pen.write(message, align="left", font=("Arial", size, "normal"))
def draw_grid(pen, grid):
"""
A procedure to draw the grid on screen using Python's `turtle` module.
"""
for i in range(0, 10):
pen.pensize(3 if i % 3 == 0 else 1)
pen.penup()
pen.goto(TOP_LEFT_X, TOP_LEFT_Y - i * CELL_SIZE)
pen.pendown()
pen.goto(TOP_LEFT_X + 9 * CELL_SIZE, TOP_LEFT_Y - i * CELL_SIZE)
pen.penup()
pen.goto(TOP_LEFT_X + i * CELL_SIZE, TOP_LEFT_Y)
pen.pendown()
pen.goto(TOP_LEFT_X + i * CELL_SIZE, TOP_LEFT_Y - 9 * CELL_SIZE)
for row_index, row in enumerate(grid):
for column_index, value in enumerate(row):
if value != 0:
draw_text(
pen,
value,
TOP_LEFT_X + column_index * CELL_SIZE + 9,
TOP_LEFT_Y - row_index * CELL_SIZE - CELL_SIZE + 8,
18,
)
def main():
grid = [[0] * 9 for _ in range(9)]
pen = turtle.Turtle()
turtle.tracer(0)
pen.speed(0)
pen.color("black")
pen.hideturtle()
fill_grid(grid, list(range(1, 10)))
draw_grid(pen, grid)
turtle.update()
sleep(1)
#
# Start Removing Numbers one by one.
#
# A higher number of attempts will end up removing more numbers from the
# grid potentially resulting in more difficiult grids to solve!
#
attempts = 5
while attempts:
#
# Select a random cell that is not already empty.
#
assert any(any(row) for row in grid)
while True:
row_index, column_index = randint(0, 8), randint(0, 8)
if grid[row_index][column_index] != 0:
break
#
# Remember the cell value in case we need to put it back.
#
removed_value = grid[row_index][column_index]
grid[row_index][column_index] = 0
solution_count = solve_grid(list(map(list, grid)))
if solution_count != 1:
grid[row_index][column_index] = removed_value
attempts -= 1
pen.clear()
draw_grid(pen, grid)
turtle.update()
print("Sudoku Grid Ready")
turtle.done()
if __name__ == "__main__":
main()