Quellcode in Excel-VBA von Sudoku-Muff v0.93 REV 0' ----------------------------------------------------------------------------------------------------------------- ' - selbstgestricktes Sudoku-Lösungsprogramm V0.93 - (c) 01/2010-02/2010 by Magmuffa Philissog - www.Issog.com - ' - REV 1 - ' - Christian Gossmann (FÜR NICHT KOMMERZIELLE ZWECKE!) - ' ----------------------------------------------------------------------------------------------------------------- ' - - ' - ddd --- ddd - Stellen beinhalten Debug-Code, der an der entsprechenden Stelle nützlich sein könnte (Ausgabe) - ' - - ' - xxx --- xxx - Stellen müssen an lokale Gegebenheiten angepasst werden - ' - - ' - yyy --- yyy - Stellen beinhalten Verbesserungsvorschläge - ' - - ' - zzz --- zzz - Stellen beinhalten Warnungen und / oder bekannte Fehler - ' - - ' ----------------------------------------------------------------------------------------------------------------- ' Entwicklungsgeschichte u. Bugfixes (=wichtigste Erweiterungen u. Änderungen): ' aktuell: v0.93 REV 1 ' -------------------- ' v0.93 REV 1: Blattschutz (ohne Passwort) gesetzt, damit eben nicht sofort die Strukturen überschrieben werden ' kleinere Anpassungen wegen dem Blattschutz an den Makros vorgenommen ' außer dem Wikipedia-Samurai-Sudoku die anderen zwecks besserer Übersichtlichkeit hinausgeworfen ' v0.93 REV 0: bessere Angabenprüfung (mind. 17 Angaben, kein leeres Angabenfeld) ' Lösungsversuchsanzahl wird angezeigt beim Backtracing (kann aber auskommentiert werden, damit das Lösungs-Geschwindigkeit bringt) ' Symbolleiste in Excel für Lösung und Löschung, aut. Tastenzuweisung ' automatischer Abbruch bei Überschreitung d. max. Lösungsanzahl erfolgt jetzt (davor musste man. STRG-Untbr. gedrückt werden, was ohne Hinweis Endlosschleife bedeutete) ' Bugfix f. weitere falsche Hervorhebung fehlerhafter Angaben in Arealen (Abbruch hat aber auch in Vorversion weiterhin korrekt gearbeitet) ' v0.92BETA REV A: Gesamtlöschroutine für Angaben- u. Lösungsfeld samt allfälligen Hervorhebungen ' Bugfixing f. Buchstabenangabe in Arealen - es konnte eine falsche Zahl hervorheben werden -> neuer Fehler: bei illegalen Zahlen wurde dann nur 1 hervorgehoben (behoben in Nachfolgeversion) ' v0.92BETA : Ausgabenroutinen verbessert (=nicht überall ausgeben lassen, sondern nur am Ende und als Debug-Code deklariert -> kann bei Bedarf eingebunden werden) ' Backtracing-Algorithmus-Fehler behoben und verbessert (hört nicht nach erstem Logiklösungsversuch schon auf, sondern macht weiter, so lange "etwas geht" und hört erst dann auf wenn "nichts mehr geht" -> dann wird nächste Zahl versucht) - löst daher noch mehr Sudokus (ist aber leider dafür langsamer auf diese Weise) ' erster quellcodeoffener Sudoku-Muff ' v0.91a : erstmals Illegal-Prüfung für Angabenfeld eingebaut u. Hervorhebungslöschroutine f. allfällige illegale Felder, die gefunden wurden ' v0.91 : Backtracing-Algorithmus eingebaut - löst nun mehr Sudokus ' Löschung für Angaben- u. Lösungsfeld per Tastendruck ' v0.9a : ausschließlich Logik-Lösungsalgorithmus - löste relativ wenige Sudokus, dafür die "Klassiker" - alle anderen sind sowieso "menschenunwürdig" und nur etwas für Computer ' Sudoku-Muff rechnet ausschließlich mit Longs, da Shorts bzw. Integers nur früher (C-Versionen f. DOS bzw. Pascal / Delphi usw...) auf 80286ern schneller waren - auf modernen PCs ab 80386 sind Longs dagegen schneller (auch, wenn die nicht gebraucht werden) ' yyy --- yyy allgemein könnte man den Algorithmus schneller machen, indem nach der Einsetzung einer gefundenen Zahl, diese aus den Reihen, Spalten u. Arealen herausgestrichen wird und nicht immer frische Möglichkeiten gesucht werden (braucht ja seine Zeit...) Option Explicit ' alle Variablen sollten "gedimmt" sein - Absturzvorbeugung und guter Programmierstil Const C_areas As Long = 3 Const C_columns As Long = 2 Const C_rows As Long = 1 ' anstatt true / false wegen Longverwendung die entsprechenden Werte von 1 und 0 verwenden (bei Index 0 der Zahlenspeicherfelder steht die Anzahl der einsetzbaren Zahlen - dafür reicht true/false eben nicht) Const C_no_solution_found As Long = 0 Const C_number_impossible As Long = 0 Const C_number_possible As Long = 1 Const CS_sudoku_bar_name As String = "Sudoku-Muff-Regiezentrum" ' Name für Icon-Leiste v. Sudoku-Muff ' yyy --- yyy X-Sudoku-Vorbereitung (Koordinaten) ' Dim LA_X1_coord(9) As Long ' Dim LA_X2_coord(9) As Long Dim L_empty_field As Long ' hier wird eine gefundene noch nicht ausgefüllte Sudoku-Koordinate geliefert ' generelle Indizes auf das jeweilige Zahlenspeicherfeld der bereits eingesetzten bzw. frisch dazuberechneten Sudoku-Ziffer anlegen (diese Variablen dienen der Adressierung der Felder) Dim L_act_GivenCalculated_area_index As Long ' Areale Dim L_act_GivenCalculated_column_index As Long ' Spalten Dim L_act_GivenCalculated_row_index As Long ' Zeilen Dim BA_filled_in(81) As Boolean ' vermerkte ausgefüllte Positionskoordinaten des Sudokus Dim BA_illegal_sudoku_field(81) As Boolean ' hier werden illegale Sudoku-Felder vermerkt, d.h. Doppelkandidaten von Ziffern, die gegen Sudoku-Regeln verstoßen Dim BA_non_numeric_field(81) As Boolean ' hier zwecks Prüfung festhalten, ob ein Wert keine Zahl ist -> wird dann als illegal markiert Dim L_filled_in_counter As Long ' Zählerstand der ausgefüllten Sudoku-Felder ' die eigentlichen Zahlenspeicher ab hier anlegen (Backups folgen danach) Dim LA_areas(9, 9) As Long ' 9 Quadrat-Areale zu je 9 Ziffern Dim LA_columns(9, 9) As Long ' 9 Spalten zu je 9 Ziffern ' der Aufbau des Possible-Areas: Index 0 = Anzahl der möglichen einsetzbaren Ziffern an der entsprechenden Sudoku-Koordinate darin (0=keine möglich, d.h. Feld ist ausgefüllt), Indizes 1-9: 0 = Ziffer nicht möglich, 1 = Ziffer möglich Dim LA_possibles(81, 9) As Long ' 81 Sudoku-Felder zu je 9 möglichen Ziffern darauf (1-9) Dim LA_rows(9, 9) As Long ' 9 Zeilen zu je 9 Ziffern ' f. Backtrace-Algorithmus Sicherungsfelder anlegen, falls das Sudoku mit einem Einsetzversuch einer Zahle nicht lösbar sein sollte, damit es wiederhergestellt werden kann ' (ab hier die BACKUP-Variablen) Dim BA_filled_in_BAK(81) As Boolean ' vermerkte ausgefüllte Positionskoordinaten des Sudokus Dim L_filled_in_counter_BAK As Long ' Zählerstand der ausgefüllten Sudoku-Felder Dim LA_areas_BAK(9, 9) As Long ' 9 Quadrat-Areale zu je 9 Ziffern Dim LA_columns_BAK(9, 9) As Long ' 9 Spalten zu je 9 Ziffern Dim LA_possibles_BAK(81, 9) As Long ' 81 Sudoku-Felder zu je 9 möglichen Ziffern darauf (1-9) Dim LA_rows_BAK(9, 9) As Long ' 9 Zeilen zu je 9 Ziffern Dim L_found_index_position As Long ' Fundstellenvariable für Backtracing-Algorithmus (dort wird dann eine Ziffer einzusetzen versucht) Dim L_last_tried_number As Long ' die Ziffer, die versucht wird, dort einzusetzen ' d. Funktion überprüft, ob schon von read_given_sudoko ungültige Werte entdeckt wurden bzw. prüft selbser noch genauer nach Function check_illegals() As Boolean ' Schleifvarialben Dim x As Long Dim y As Long Dim z As Long Dim L_sudoku_field_index As Long ' hier wird die aktuell bearbeitete Sudoku-Koordinate gespeichert check_illegals = False ' davon ausgehen, dass Funktion korrekt ohne Duplikatenfund oder ungültige Fundwerte durchläuft - falls doch etwas gefunden wird, wird das Flag auf true gesetzt For x = 1 To 9 ' 9 Reihen, Spalten bzw. Areale abgrasen For y = 1 To 8 ' 9 Indices werden miteinander vergleichen (Schleife nur bis 8, da 9. Element mit dem 8. verglichen wird, d.h. +1 im Vergleich) ' Reihen L_sudoku_field_index = get_sudoku_field_index(C_rows, x, y) ' Sudoku-Koordinate aus den akt. Ziffernspeichern ermitteln If BA_illegal_sudoku_field(L_sudoku_field_index) = True Then ' hat read_given_sudoku bereits etwas markiert? check_illegals = True ' ja, dann Funktionsergebnis auf true setzen -> fehlerhafte Eingabe entdeckt GoTo next_loop ' weiter bei nächster Sudoku-Koordinate, da einmal illegal, immer illegal und untere Tests uninteressant auf dieser Position (allfällige identische Buchstabeneingabe wurde ja auch von read_given_sudoku als illegal eingestuft, d.h. Vergleich überflüssig - dient nur korrekten, aber doppelten Ziffern) End If For z = 0 + y To 8 ' 8 Vergleiche pro Reihe / Spalte / Areal außer bereits verglichene Positionen ab Anfang (Selbstvergleichsverhinderung), da es insg. 9 Elemente gibt und jedes mit jedem "bubblesortmäßig" verglichen wird If LA_rows(x, y) <> 0 And LA_rows(x, y) = LA_rows(x, z + 1) Then ' steht dort was von 0 Verschiedenes drinnen und ist dieses "Etwas" gleich einem anderen "Etwas" (0-Werte dürfen ja drinnen sein, werden aber ignoriert) BA_illegal_sudoku_field(L_sudoku_field_index) = True ' ja, dann darf dies aber gem. den Sudoku-Regeln nicht sein - oben ermitteltes akt. Bearbeitungsfeld als illegal markieren L_sudoku_field_index = get_sudoku_field_index(C_rows, x, z + 1) ' frisch die Sudoku-Koordinate auf das verglichene Feld ermitteln BA_illegal_sudoku_field(L_sudoku_field_index) = True ' weil dieses natürlich auch fehlerhaft ist (=handelt sich ja um ein gefundenes Duplikat) check_illegals = True ' Duplikatenfund vermerken als Funktionsrückgabecode End If Next z ' weitermachen, bis alles verglichen ist ' Spalten L_sudoku_field_index = get_sudoku_field_index(C_columns, x, y) ' Sudoku-Koordinate aus den akt. Ziffernspeichern ermitteln For z = 0 + y To 8 ' 8 Vergleiche pro Reihe / Spalte / Areal außer bereits verglichene Positionen ab Anfang (Selbstvergleichsverhinderung), da es insg. 9 Elemente gibt und jedes mit jedem "bubblesortmäßig" verglichen wird If LA_columns(x, y) <> 0 And LA_columns(x, y) = LA_columns(x, z + 1) Then ' steht dort was von 0 Verschiedenes drinnen und ist dieses "Etwas" gleich einem anderen "Etwas" (0-Werte dürfen ja drinnen sein, werden aber ignoriert) BA_illegal_sudoku_field(L_sudoku_field_index) = True ' ja, dann darf dies aber gem. den Sudoku-Regeln nicht sein - oben ermitteltes akt. Bearbeitungsfeld als illegal markieren L_sudoku_field_index = get_sudoku_field_index(C_columns, x, z + 1) ' frisch die Sudoku-Koordinate auf das verglichene Feld ermitteln BA_illegal_sudoku_field(L_sudoku_field_index) = True ' weil dieses natürlich auch fehlerhaft ist (=handelt sich ja um ein gefundenes Duplikat) check_illegals = True ' Duplikatenfund vermerken als Funktionsrückgabecode End If Next z ' weitermachen, bis alles verglichen ist ' Areale L_sudoku_field_index = get_sudoku_field_index(C_areas, x, y) ' Sudoku-Koordinate aus den akt. Ziffernspeichern ermitteln For z = 0 + y To 8 ' 8 Vergleiche pro Reihe / Spalte / Areal außer bereits verglichene Positionen ab Anfang (Selbstvergleichsverhinderung), da es insg. 9 Elemente gibt und jedes mit jedem "bubblesortmäßig" verglichen wird If LA_areas(x, y) <> 0 And LA_areas(x, y) = LA_areas(x, z + 1) Then ' steht dort was von 0 Verschiedenes drinnen und ist dieses "Etwas" gleich einem anderen "Etwas" (0-Werte dürfen ja drinnen sein, werden aber ignoriert) BA_illegal_sudoku_field(L_sudoku_field_index) = True ' ja, dann darf dies aber gem. den Sudoku-Regeln nicht sein - oben ermitteltes akt. Bearbeitungsfeld als illegal markieren L_sudoku_field_index = get_sudoku_field_index(C_areas, x, z + 1) ' frisch die Sudoku-Koordinate auf das verglichene Feld ermitteln BA_illegal_sudoku_field(L_sudoku_field_index) = True ' weil dieses natürlich auch fehlerhaft ist (=handelt sich ja um ein gefundenes Duplikat) check_illegals = True ' Duplikatenfund vermerken als Funktionsrückgabecode End If Next z ' weitermachen, bis alles verglichen ist next_loop: ' yyy --- yyy allenfalls die X-Reihen bei X-Sudoku-Erweiterung hier behandeln lassen (ACHTUNG - sind nur 2 Stück -> eigene 2-er-Schleife daher!) Next y ' nächster Index Next x ' nächste(s) Reihe / Spalte / Areal End Function ' check_illegals ' d. Sub liest ein gegebenes Sudoku ein - dieser Programmteil muss bei Transpilation in andere Programmiersprachen / Systeme (nicht Excel) angepasst werden ' wichtig ist dabei, dass am Schluss die bereits gegebenen Sudoku-Zahlen in den Datenfeldern für Zeilen, Spalten u. Areale stehen ' die reinen Berechnungs- u. Lösungsalgorithmen dagegen brauchen nur 1:1 in die jeweilige Zielprogrammiersprache übertragen werden ' die Ausgaberoutine der gefundenen und fertig generierten Datenfelder muss jedoch wieder an lokale Gegebenheiten angepasst werden Private Sub read_given_sudoku(L_row, L_column As Long) ' xxx --- xxx bei Änderung der Sudoku-Größe bzw. Position hier die Konstanten ändern Const C_given_sudoku_start_column = 14 Const C_sudoku_start_row = 2 ' Spalten und Zeilen anlegen Dim L_startcolumn As Long Dim L_startrow As Long Dim L_sudoku_field_index As Long Dim L_read_value As Long ' Laufvariablen für die Schleifen anlegen (nehmen je nachdem Sudoku-Feld-Koordinaten, Zeilen bzw. Spalten oder Ziffern auf) Dim x As Long Dim y As Long Dim z As Long On Error Resume Next ' falls Stringwerte in die Longs eingelesen werden sollten, diesen Fehler abfangen lassen (passieren kann nichts, da check_illegals auf die Strings aufmerksam macht, da diese ja hier schon 1. gar nicht eingelesen u. 2. in BA_non_numeric_field vermerkt(xx) werden) ' Startspalte und -zeile initialisieren (wo die Sudoku-Werte eben in der Excel-Tabelle beginnen) ' xxx --- xxx bei Erweiterung anpassen L_startcolumn = C_given_sudoku_start_column ' Zuweisung der Konstante für die Startspalten L_startrow = C_sudoku_start_row ' Zuweisung der Konstante für die Startzeile ' hier wird zuerst ein Feld angelegt, das vermerkt, ob an einem bestimmten Sudoku-Feld (1-81) überhaupt ein Wert ausgefüllt ist ' außerdem wird die Anzahl ausgefüllter Felder addiert, damit man weiß, wennn alle 81 ausgefüllt sind -> dann ist das Sudoku gelöst L_filled_in_counter = 0 ' zuerst von keinem vorausgefüllten Feld ausgehen y = 0 ' Startspalte innerhalb des Sudokus auf 0 setzen z = 0 ' Startzeile innerhalb des Sudokus auf 0 setzen For x = 1 To 81 ' 81 Sudoku-Felder abgrasen für Test auf ungültige Angabendaten ' xxx --- xxx bei Erweiterung umcodieren, wenn alphanumerische Sudokus gelöst werden sollen (yyy --- yyy: Empfehlung: Symbolen Zahlen zuweisen und weiterhin mit Zahlen rechnen lassen, sonst wird man deppert -> intern dann aus einem Zahlenspeicher die Zahlen des Sudokus lesen lassen) If IsNumeric(Cells(L_startrow + z, L_startcolumn + y)) = False Then ' keine Zahl vorhanden? BA_illegal_sudoku_field(x) = True ' ja, Feld als ungültig vermerken BA_filled_in(x) = False ' Feld auch als nicht ausgefüllt vermerken ' yyy --- yyy später hier Umwandlung in Zahlen durchführen f. Sudoku-Erweiterung um Alpha-Zeichen BA_non_numeric_field(x) = True ' es liegt kein numerischer Wert vor -> vermerken im Alphazeichenfeld GoTo next_loop Else BA_non_numeric_field(x) = False ' numerischer Wert liegt vor -> vermerken für Highlight-Ausgabentest, damit 0 igoriert wird End If L_read_value = Cells(L_startrow + z, L_startcolumn + y) If L_read_value >= 0 And L_read_value < 10 Then ' steht im betreffenden Feld ein gültiger Ziffernwert? BA_illegal_sudoku_field(x) = False ' ja, Feld als gültig vermerken If L_read_value <> 0 Then BA_filled_in(x) = True ' Feld als korrekt ausgefüllt vermerken L_filled_in_counter = L_filled_in_counter + 1 ' Anzahl der ausgefüllten Felder erhöhen (sind nämlich 81 von 81 am Schluss ausgefüllt, ist das Sudoku gelöst) End If Else BA_illegal_sudoku_field(x) = True ' ansonsten Feld als ungültig vermerken ' zzz --- zzz diese Stellen dahingehend checken, ob wenn diese Initialisierung weggelassen werden alles korrekt weiterläuft (Feld = sowieso illegal, d.h. es wird abgebrochen werden mit dank check_illegals und highlight_illegals) BA_filled_in(x) = False ' Feld auch als nicht ausgefüllt vermerken End If next_loop: y = y + 1 ' Spaltenzähler erhöhen If x Mod 9 = 0 Then ' 9 Zahlen / Reihe -> wenn ohne Rest teilbar, dann neue Zeile abrasen y = 0 ' dazu Spaltenzähler wieder auf 0 setzen z = z + 1 ' Zeilenzähler dagegen auf nächste Sudoku-Zeile setzen End If Next x ' nächstes Sudoku-Feld bearbeiten ' genereller Hinweis: in VBA werden allfällig eingelesene Alphawerte (von Zahlen verschieden) als 0 eingelesen - darum ist oben ja ohnehin schon das betreffende Feld als illegal eingestuft für check_illegals ' hier werden die horizontalen und veritikalen Sudoku-Reihen eingelesen For x = 0 To 8 ' je 9 Zeilen / Spalten For y = 0 To 8 ' und 9 Spalten / Zeilen LA_rows(x + 1, y + 1) = Cells(L_startrow + x, L_startcolumn + y) ' jeweiligen Zeilenwert einlesen LA_columns(x + 1, y + 1) = Cells(L_startrow + y, L_startcolumn + x) ' jeweiligen Spaltenwert einlesen Next y ' nächste Spalte / Zeile Next x ' nächste Zeile / Spalte ' xxx --- xxx alle Zahlen entsprechend anpassen bei Sudoku-Größenänderung ' hier werden die 9 Quadrat-Areale des Sudokus mit je 3 untereinanderliegenden Zahlenreihen zu je 3 Ziffern eingelesen For z = 0 To 6 Step 3 ' eine Quadrat-Areal-Reihe hat horizontal 3 Ziffern For x = 0 + z To 2 + z ' 3 Quadrat-Areale / Sudoku-Zeile sind betroffen For y = 0 To 2 ' jeweils 3 Zahlen davon / Quadrat-Areal zuordnen L_sudoku_field_index = get_sudoku_field_index(C_areas, x + 1, 1 + y) ' Sudoku-Koordinate aus den akt. Ziffernspeichern ermitteln If BA_non_numeric_field(L_sudoku_field_index) = False Then ' prüfen, ob dort tatsächlich eine Zahl steht -> Vermeidung von Buchstaben / Zeicheneinlesung (für check_illegals, da Zeichencodes dann in den Longs vorkommen und die zufällig auch mit Ziffern übereinstimmen könnten -> illegale Stellen markiert, wo keine sind) LA_areas(x + 1, 1 + y) = Cells(L_startrow, L_startcolumn + y) ' jeweils 1., 2. u. 3. Ziffer des 1. betroffenen Quadrat-Areals einlesen End If L_sudoku_field_index = get_sudoku_field_index(C_areas, x + 1, 4 + y) ' Sudoku-Koordinate aus den akt. Ziffernspeichern ermitteln If BA_non_numeric_field(L_sudoku_field_index) = False Then ' prüfen, ob dort tatsächlich eine Zahl steht -> Vermeidung von Buchstaben / Zeicheneinlesung (für check_illegals, da Zeichencodes dann in den Longs vorkommen und die zufällig auch mit Ziffern übereinstimmen könnten -> illegale Stellen markiert, wo keine sind) LA_areas(x + 1, 4 + y) = Cells(L_startrow + 1, L_startcolumn + y) ' jeweils 1., 2. u. 3. Ziffer des 2. betroffenen Quadrat-Areals einlesen End If L_sudoku_field_index = get_sudoku_field_index(C_areas, x + 1, 7 + y) ' Sudoku-Koordinate aus den akt. Ziffernspeichern ermitteln If BA_non_numeric_field(L_sudoku_field_index) = False Then ' prüfen, ob dort tatsächlich eine Zahl steht -> Vermeidung von Buchstaben / Zeicheneinlesung (für check_illegals, da Zeichencodes dann in den Longs vorkommen und die zufällig auch mit Ziffern übereinstimmen könnten -> illegale Stellen markiert, wo keine sind) LA_areas(x + 1, 7 + y) = Cells(L_startrow + 2, L_startcolumn + y) ' jeweils 1., 2. u. 3. Ziffer des 3. betroffenen Quadrat-Areals einlesen End If Next y L_startcolumn = L_startcolumn + 3 ' Startspalte für jeweils nächste Quadrat-Areal-Ziffer befindet sich jeweils 3 Ziffern weiter rechts Next x L_startrow = L_startrow + 3 ' Startzeile für jeweils nächstes Quadrat-Areal befindet sich jeweils 3 Ziffern unter aktueller Quadrat-Areale-Zeile L_startcolumn = C_given_sudoku_start_column ' Startspalte wieder auf 1. Ziffer setzen (=ganz links) Next z On Error GoTo 0 ' Errorhandler wieder deaktivieren (hier konnten nur Einlesefehler der Art "Unsinn" in Longs auftreten...) End Sub ' read_given_sudoku ' d. Sub sichert den aktuellen Sudoku-Stand in den Backup-Variablen ' yyy --- yyy hier könnte man durch DMA bzw. Maschinesprache ein gewaltiges Geschwindigkeitsplus herausholen (nicht in VBA!) Private Sub backup_sudoku() Dim x As Long ' Kopierschleifenvarialben Dim y As Long For x = 0 To 9 ' vollständige (inkl. 0-Index f. allfällige Zusatz-Daten...) Sicherung d. Reihen, Spalten, Areale For y = 0 To 9 ' vollständige (inkl. 0-Index f. allfällige Zusatz-Daten...) Sicherung d. Ziffern 1-9 LA_rows_BAK(x, y) = LA_rows(x, y) ' Reihen bearbeiten LA_columns_BAK(x, y) = LA_columns(x, y) ' Spalten bearbeiten LA_areas_BAK(x, y) = LA_areas(x, y) ' Areale bearbeiten Next y Next x For x = 0 To 81 ' auch den akt. Ausfüllstatus der 81 Sudoku-Felder sichern BA_filled_in_BAK(x) = BA_filled_in(x) ' Statusfeld bearbeiten Next x L_filled_in_counter_BAK = L_filled_in_counter ' auch akt. Füllstand sichern For x = 0 To 81 ' auch den akt. Möglichkeitenstatus sichern For y = 0 To 9 LA_possibles_BAK(x, y) = LA_possibles(x, y) ' sichern Next y Next x End Sub ' backup_sudoku ' d. Sub stellt den aktuellen Sudoku-Stand aus den Backup-Variablen wieder her ' yyy --- yyy hier könnte man durch DMA bzw. Maschinesprache ein gewaltiges Geschwindigkeitsplus herausholen (nicht in VBA!) Private Sub restore_sudoku() Dim x As Long ' Kopierschleifenvarialben Dim y As Long For x = 0 To 9 ' vollständige (inkl. 0-Index f. allfällige Zusatz-Daten...) Wiederherstellung d. Reihen, Spalten, Areale For y = 0 To 9 ' vollständige (inkl. 0-Index f. allfällige Zusatz-Daten...) Wiederherstellung d. Ziffern 1-9 LA_rows(x, y) = LA_rows_BAK(x, y) ' Reihen bearbeiten LA_columns(x, y) = LA_columns_BAK(x, y) ' Spalten bearbeiten LA_areas(x, y) = LA_areas_BAK(x, y) ' Areale bearbeiten Next y Next x For x = 0 To 81 ' auch den akt. Ausfüllstatus der 81 Sudoku-Felder sichern BA_filled_in(x) = BA_filled_in_BAK(x) ' Statusfeld bearbeiten Next x L_filled_in_counter = L_filled_in_counter_BAK ' auch akt. Füllstand wieder herstellen For x = 0 To 81 ' auch den akt. Möglichkeitenstatus wiederherstellen For y = 0 To 9 LA_possibles(x, y) = LA_possibles_BAK(x, y) ' wiederherstellen Next y Next x End Sub ' restore_sudoku ' d. Funktion sucht die möglichen einsetzbare Ziffer basierend auf den bisher gegebenen und schon dazuberechneten (d.h. also, wie es weitergeht) Function generate_possibles() As Long Dim L_act_index As Long ' die akt. Index-Position im jeweiligen Zahlenspeicherfeld (wo entweder vermerkt wird, oder nicht) Dim L_number As Long ' die eigentliche gefundene Ziffer Dim L_possibles_counter As Long ' die Anzahl der möglichen einsetzbaren Ziffern Dim L_sudoku_field_index As Long ' die akt. Sudoku-Koordinate, an der die entsprechenden Ziffern gebildet werden ' Possibles-Feld löschen, damit keine Störrreste drinnen bleiben (ist z.B. der Fall beim Wikipedia-Sudoku "rechts oben" an Koordinate 69 -> 9 wird dann illegalerweise geliefert -> Wahrscheinlichkeit hoch, dass auch andere Sudokus mit derartigem Fehler möglich sind) ' yyy --- yyy hier könnte man durch DMA bzw. Maschinesprache ein gewaltiges Geschwindigkeitsplus herausholen (nicht in VBA!) For L_sudoku_field_index = 1 To 81 ' 81 Sudoku-Felder durchlaufen For L_act_index = 0 To 9 ' jeweils 9 potentielle Ziffern darin LA_possibles(L_sudoku_field_index, L_act_index) = 0 ' 0-Setzung durchführen Next L_act_index ' nächsten Ziffernspeicher bearbeiten Next L_sudoku_field_index ' nächste Sudoku-Koordinate bearbeiten ' jeweils 9 mögliche Reihen zu je 9 möglichen Ziffern auf 9 möglichen Koordinaten in jeweils 9 möglichen Indexfeldern speichern (=Möglichkeitsspeicher) For L_sudoku_field_index = 1 To 81 ' 81 Sudoku-Felder durchlaufen If BA_filled_in(L_sudoku_field_index) = True Then ' steht an der entsprechenden Sudoku-Koordinate schon eine Zahl? GoTo next_loop ' ja, dann ist es überflüssig, weiterzumachen (und verlangsamt die Suche nach möglichen einsetzbaren Ziffern nur: es gäbe hier keine) -> aus Geschwindigkeitsgründen daher gleich zur nächsten Koordinate springen End If Call get_indices(L_sudoku_field_index) ' aktuelle Indizes anhand der Sudoku-Koordinate auf das jeweilige Zahlenspeicherfeld ermitteln L_possibles_counter = 9 ' zuerst davon ausgehen, dass auf der jeweiligen Koordinate alle Ziffern von 1-9 eingesetzt werden können For L_number = 1 To 9 ' jeweils 9 mögliche Ziffern durchlaufen For L_act_index = 1 To 9 ' jeweils 9 Index-Einträge in den bereits gegebenen / errechneten Zeilen- und Spaltenspeicher-Feldern abgrasen (=damit vergleichen, ob aktuelle Zahl darin vorkommt) If L_number = LA_rows(L_act_GivenCalculated_row_index, L_act_index) Then ' aktuelle Ziffer in Reihenkombination nicht einsetzbar (=weil sie schon einmal vorkommt)? LA_possibles(L_sudoku_field_index, L_number) = C_number_impossible ' ja, dann diese Ziffer als unmöglich einstufen L_possibles_counter = L_possibles_counter - 1 ' restliche Möglichkeiten auf restliche Anzahl an Ziffern beschränken Exit For ' weiter mit nächster Zahl, da weitere Tests auf DIESE Zahl uninteressant (einmal unmöglich gewesen = immer unmöglich an der Stelle im Sudoku) End If If L_number = LA_columns(L_act_GivenCalculated_column_index, L_act_index) Then ' aktuelle Ziffer in Spaltenkombination nicht einsetzbar (=weil sie schon einmal vorkommt)? LA_possibles(L_sudoku_field_index, L_number) = C_number_impossible ' ja, dann diese Ziffer als unmöglich einstufen L_possibles_counter = L_possibles_counter - 1 ' restliche Möglichkeiten auf restliche Anzahl an Ziffern beschränken Exit For ' weiter mit nächster Zahl, da weitere Tests auf DIESE Zahl uninteressant (einmal unmöglich gewesen = immer unmöglich an der Stelle im Sudoku) End If If L_number = LA_areas(L_act_GivenCalculated_area_index, L_act_index) Then ' aktuelle Ziffer in Arealkombination nicht einsetzbar (=weil sie schon einmal vorkommt)? LA_possibles(L_sudoku_field_index, L_number) = C_number_impossible ' ja, dann diese Ziffer als unmöglich einstufen L_possibles_counter = L_possibles_counter - 1 ' restliche Möglichkeiten auf restliche Anzahl an Ziffern beschränken Exit For ' weiter mit nächster Zahl End If ' yyy --- yyy hier allenfalls den Einbau einer X-Sudoku-Testbedingung einbauen LA_possibles(L_sudoku_field_index, L_number) = C_number_possible ' Unmöglichkeit der akt. Ziffer in der Schleife nicht festgestellt, dann die aktuelle Ziffer im Feld als einsetzbar vermerken Next L_act_index ' weiter mit der gleichen Zahl bei der nächsten Indexposition der Reihen-, Spalten- und Arealspeicher - ob sie dort vorkommt LA_possibles(L_sudoku_field_index, 0) = L_possibles_counter ' aktuellen Stand der möglichen einsetzbaren Zahlen an Indexstelle 0 vermerken (0 wird in einem Sudoku nicht verwendet - der Platz dient daher als Anzahlspeicher) If L_possibles_counter = 1 Then ' wurde beim Durchlauf der Zahlen bereits eine einzige verbleibende Möglichkeit gefunden L_found_index_position = L_sudoku_field_index ' ja, dann die gefundene Sudoku-Koordinate in der Globalvariable liefern For L_act_index = 1 To 9 ' die einzige verbleibende Nummer jetzt heraussuchen (im Fall des Wikipedia-Sudokus "rechts oben", wird hier aber nichts gefunden) If (LA_possibles(L_sudoku_field_index, L_act_index)) = C_number_possible Then ' Suche nach der einzelnen gefundenen Zahl vornehmen generate_possibles = L_act_index ' im Funktionscode die gefundene Einzelzahl vermerken: Hauptprogramm > 0 = Signal, dass eine Sofortzahl da ist Exit Function ' Einzelziffer gefunden -> Funktionsende (nach Einsetzung im Hauptprogramm ändern sich ohnehin wieder die Möglichkeiten, d.h. die werden dann frisch generiert) End If Next L_act_index ' weitermachen, bis die Zahl gefunden ist End If Next L_number next_loop: Next L_sudoku_field_index generate_possibles = C_no_solution_found ' es wurde (vorerst) keine eindeutige Zahl bei der Möglichkeitsbildung gefunden -> muss Hauptprogramm nun mit Lösungsalgorithmen machen (=einmaliger Fund einer möglichen Zahl in Reihen oder Spalten bzw. im Extremfall auf Backtracing gehen) End Function ' generate_possibles ' d. Sub zeigt d. Copyright-Hinweis (und allenfalls weitere Informationen an) Private Sub display_info_and_copyright() MsgBox ("Sudoku-Muff v0.93 - REV 1, (c) 2010 by Muffa Issog, www.issog.com / keine kommerzielle, sondern nur akademische Verwendung - no commercial use, for academic purposes only") ' Anweisung hier sicherheitshalber, damit ja alle Variablenreste gelöscht werden - für wiederholte Makroaufrufe End End Sub ' d. Sub dient f. den Aufruf aus der Symbolleiste, damit daraus eine Abfrage gestellt werden kann, die dann allenfalls die Symbolleiste wie beim Datei-Schließen ausblendet Private Sub autoclose_caller() If MsgBox("Soll die Sudoku-Muff-Symbolleiste wirklich ausgeblendet werden? Falls ja, bringt Sie STRG-SHIFT-B wieder hervor...", vbOKCancel) = vbOK Then Call auto_close End If End ' Anweisung hier sicherheitshalber, damit ja alle Variablenreste gelöscht werden - für wiederholte Makroaufrufe End Sub ' d. Funktion berechnet anhand eines Index auf Reihen-, Spalten bzw. Arealspeicher die jeweilige korrespondierende Sudoku-Koordinate (=somit das Gegenteil von get_indices) Function get_sudoku_field_index(RCA, index_x, index_y As Long) As Long ' Variablen zur Berechnung der Sudoku-Koordinate aus der Areal-Koordinate anlegen Dim L_area_x As Long Dim L_area_y As Long Dim L_area_column As Long Dim L_area_row As Long Dim L_sudoku_coordinate As Long ' RCA (1 = Reihen, 2 = Spalten, 3 = Areale) ' index_x = Indexnummer d. jeweiligen Speicherfeldes (=Reihe-, Spalten- bzw. Arealnummer) ' index_y = Index innerhalb d. jeweiligen Speicherfeldes (der eigentliche Ziffernspeicher) Select Case RCA ' Berechnungswunsch? Case 1 ' -> Reihen (einfach mit Formel) get_sudoku_field_index = index_x * 9 - (9 - index_y) Exit Function ' fertig / Achtung: beim Transpilieren in C/Java & Co. "break" geben, damit die Case-Anweisung nicht "durchfällt" (in VBA nicht erforderlich - steht hier zur Geschwindigkeitsoptimierung) zzz --- zzz Case 2 ' -> Spalten (einfach mit Formel) get_sudoku_field_index = index_y * 9 - (9 - index_x) Exit Function ' fertig / Achtung: beim Transpilieren in C/Java & Co. "break" geben, damit die Case-Anweisung nicht "durchfällt" (in VBA nicht erforderlich - steht hier zur Geschwindigkeitsoptimierung) zzz --- zzz Case 3 ' -> Areale (aufwendig...) ' Areal-Spalte ermitteln L_area_x = index_x Mod 3 ' zuerst anhand der Areal-Nummer die Areal-Spalte ermitteln (=ein Vielfaches der Unterspalten), d.h. ein Rest entspricht der eigentlichen Arealnummer innerhalb einer Zeile von Arealen If L_area_x = 0 Then ' gibt es aber keinen Rest (0)? L_area_x = 3 ' ja, dann ist es die letzte Arealnummer innerhalb einer Arealzeile gewesen -> anstatt der "0" eben diese zuweisen (hier die "3", da 3 Areale pro Sudokuzeile tangiert werden) End If ' nun die Spalte innerhalb des Areals selbst ermitteln L_area_column = index_y Mod 3 ' innerhalb eines Areals ist der Spaltendivisionsrest die darin enthaltene Arealspalte If L_area_column = 0 Then ' gibt es aber keinen Rest (0)? L_area_column = 3 ' ja, dann ist es die letzte Arealspalte innerhalb des Areals gewesen -> anstatt der "0" eben diese zuweisen (hier die "3", da jeweils 3 Spalten pro Areal eine Arealzeile bilden) End If ' nun die Zeile innerhalb des Areals selbst ermitteln If index_y Mod 3 > 0 Then ' gibt es einen Rest von der Zeile innerhalb des Areals? L_area_row = Int(index_y / 3) + 1 ' ja, dann Ganzzahlquotient z.zgl. der nächsten Zeile nehmen (davaon stammt ja der Rest...) Else L_area_row = index_y / 3 ' kein Rest, dann die Ganzzahldivision durchführen - die liefert dann die Zeile innerhalb des Areals End If ' und noch die eigentliche Areal-Zeile ermitteln If index_x Mod 3 = 0 Then ' kein Rest? L_area_y = index_x / 3 ' ja, dann erst einmal die Areal-Vollzeilen ermitteln (=ganzzahliges Vielfaches der Sudoku-Areale pro Zeile) L_area_y = L_area_y - 1 ' da in diesem Fall aber nicht die max. Anzahl an Vollzeilen genommen werden dürfen wegen der Berechnung unten durch areal-interne Zeilen und Spalten, d. Areal-Vollzeilen um 1 vermindern - sonst würde eine Ganze dazugezählt werden, was hier 3*9 also 27 Koordinaten daneben bedeuten würde an Sudoku-Koordinaten Else L_area_y = Int(index_x / 3) ' bei Rest die Areal-Vollzeilen "normal" ermitteln, der "Rest" wird ja unten aus den areal-internen Zeilen und Spalten dazuberechnet (darum werden ja die areal-internen Zeilen und Spalten errechnet) End If ' jetzt sind sowohl Areal-Koordinaten, wie interne Zeile und Spalte im Areal bekannt - nun kann daraus die Sudoku-Koordinate errechnet werden ' yyy --- yyy Formel in nur einer Zeile (wäre schneller, aber unübersichtlicher...) L_sudoku_coordinate = L_area_y * 3 * 9 ' das 3-fache davon (Areale-Zeilen-Anzahl) und das Sudoku-Zeilenvielfache (9) ergibt die bestehenden Koordinaten-Vollzeilen (falls welche da sind, anonsten ist L_area_y 0 und es kommt 0 heraus, dann wird weiter anhand der vorhandenen restl. Werte berechnet - die Sudoku-Koordinate liegt dann aber innerhalb der ersten Areal-Zeile) L_sudoku_coordinate = L_sudoku_coordinate + (L_area_row - 1) * 9 L_sudoku_coordinate = L_sudoku_coordinate + (L_area_x - 1) * 3 + L_area_column get_sudoku_field_index = L_sudoku_coordinate ' errechnete Sudoku-Koordinate übergeben Exit Function ' fertig / Achtung: beim Transpilieren in C/Java & Co. "break" geben, damit die Case-Anweisung nicht "durchfällt" (in VBA nicht erforderlich - steht hier zur Geschwindigkeitsoptimierung) zzz --- zzz End Select End Function ' get_sudoku_field_index ' d. Sub berechnet anhand der Sudoku-Koordinate den Index in den jeweiligen Zeilen, Spalten bzw. Areal-Speicher und hält dies in den entsprechenden Globalvariablen fest Private Sub get_indices(L_sudoku_field_index As Long) ' Variablen zur Berechnung der akt. Sudoku-Areal-Koordinate anlegen Dim L_area_x As Long Dim L_area_y As Long L_act_GivenCalculated_row_index = Int(L_sudoku_field_index / 9) ' alle 9 Koordinaten beginnt eine neue Sudkou-Zeile (=Zeilenspeicherindex) If L_sudoku_field_index Mod 9 > 0 Then ' gibt es einen Rest? L_act_GivenCalculated_row_index = L_act_GivenCalculated_row_index + 1 ' ja, dann ist es schon die nächste Zeile End If ' aktuellen Index anhand der Sudoku-Koordinate auf den jeweiligen Spaltenspeicher ermitteln If L_sudoku_field_index Mod 9 = 0 Then ' ist die Spalte ohne Rest teilbar L_act_GivenCalculated_column_index = 9 ' ja, dann ist es automatisch die letzte Spalte (die 9.) Else L_act_GivenCalculated_column_index = L_sudoku_field_index Mod 9 ' nein, dann ist es der Rest, der übrig ist End If ' aktuellen Index anhand der Sudoku-Koordinate auf den jeweiligen Arealspeicher ermitteln L_area_x = Int(L_act_GivenCalculated_column_index / 3) ' Spaltenkoordinate (alle 3 Koordinaten beginnt eine neue Areal-Spalte) If L_act_GivenCalculated_column_index Mod 3 > 0 Then ' gibt es einen Rest? L_area_x = L_area_x + 1 ' ja, dann ist es schon die nächste Areal-Spalte End If L_area_y = Int(L_act_GivenCalculated_row_index / 3) ' Zeilenkoordinate (alle 3 Koordinaten beginnt eine neue Areal-Zeile) If L_act_GivenCalculated_row_index Mod 3 > 0 Then ' gibt es einen Rest? L_area_y = L_area_y + 1 ' ja, dann ist es schon die nächste Areal-Zeile End If ' Endberechung der Areal-Koordinate L_act_GivenCalculated_area_index = L_area_y * 3 - (3 - L_area_x) ' Areal-Zeilenvielfaches (x3) abzüglich akt. Areal-Zeilenposition = die Arealkoordinate End Sub ' get_indices ' d. Sub setzt eine Ziffer in die Sudoku-Ziffernspeicherfelder ein und passt die entsprechenden darauf basierenden Variablen an Private Sub fill_in_number(L_sudoku_field_index, L_number As Long) Dim L_area_index As Long Dim L_area_x As Long Dim L_area_y As Long Dim L_call_sudoku_field_index As Long ' nur f. (Visual) Basic eine Puffervariable deklarieren, um unten get_indices aufrufen zu können Dim L_niller As Long ' yyy --- yyy hier könnte man durch DMA bzw. Maschinesprache ein gewaltiges Geschwindigkeitsplus herausholen (nicht in VBA!) For L_niller = 0 To 9 ' alle Indizes der möglichen Zahlen für die jeweilige Sudoku-Koordinate auf 0 setzen / Index 0 ist damit automatisch gleich mit auf Möglichkeitsanzahl: 0 gesetzt LA_possibles(L_sudoku_field_index, L_niller) = 0 ' akt. eingesetze Zahl aus dem Möglichkeitspool eliminieren an dieser Stelle - für korrekten Neu-Lauf von generate_possibles - damit dort nicht die akt. Zahl drinnen bleibt (Initialisierungs-/Neusetz-Schleife wird ja bei einer ausgefüllten Sudoku-Koordinate an dieser Stelle zwecks Ausführungsgeschwindigkeit übersprungen - somit käme die Routine zum Löschen an der Stelle dort nicht zur Ausführung) Next L_niller L_call_sudoku_field_index = L_sudoku_field_index ' VBA-Spezifisches: Zwecks folgendem Aufruf den übergebenen Koordinatenwert in eine Variable lesen Call get_indices(L_call_sudoku_field_index) ' Indizes auf die Zahlenspeicher ermitteln - die ermittelte Ziffer muss jeweils in den Reihen-, Spalten- u. Arealspeicherfeldern eingesetzt werden für weitere korrekte Berechnungsläufe LA_rows(L_act_GivenCalculated_row_index, L_act_GivenCalculated_column_index) = L_number ' gefundene Ziffer im Reihenspeicherfeld einsetzen LA_columns(L_act_GivenCalculated_column_index, L_act_GivenCalculated_row_index) = L_number ' gefundene Ziffer im Spaltenspeicherfeld einsetzen ' Arealspeicherfeld fehlt noch -> aktuellen Index anhand der durch Spalten und Zeilen definierten Sudoku-Koordinate auf das jeweilige Arealspeicherfeld ermitteln L_area_x = L_act_GivenCalculated_column_index Mod 3 ' die von get_indices gelieferten Ziel-Berechnungen auf das Spaltenspeicherfeld heranziehen L_area_y = L_act_GivenCalculated_row_index Mod 3 ' die von get_indices gelieferten Ziel-Berechnungen auf das Reihenspeicherfeld heranziehen ' Areal-Zeilen- und -Spalten-Nummer-Endberechnung durchführen If L_area_x = 0 Then ' gab es keinen Rest bei den Spalten? L_area_x = 3 ' ja -> dann auf die letzte Spalten setzen (ist dann nämlich die Letze gewesen) End If If L_area_y = 0 Then ' gab es keinen Rest bei den Zeilen? L_area_y = 3 ' ja -> dann auf die letzte Zeile setzen (ist dann nämlich die Letze gewesen) End If ' yyy --- yyy L_area_index kann in die Klammer gehen und geschmissen werden auch beim DIM L_area_index = L_area_y * 3 - (3 - L_area_x) ' anhand des ermittelten Areals den Index auf das Arealspeicherfeld berechnen LA_areas(L_act_GivenCalculated_area_index, L_area_index) = L_number ' in das (noch von get_indices gelieferte) Arealspeicherfeld am errechneten Indexplatz die entsprechende Ziffer eintragen L_filled_in_counter = L_filled_in_counter + 1 ' nun ist um eine Ziffer mehr im Sudoku ausgefüllt -> den Zähler anpassen BA_filled_in(L_sudoku_field_index) = True ' damit diese Koordinate auch übersprungen werden kann bei der Möglichkeitsbildung und den Tests auf Einzelvorkommen von Zahlen darin in Reihen oder Spalten bzw. bei der Suche nach noch freien (noch nicht ausgefüllten) Koordinaten im Sudoku beim Backtracing, die "Ausfüllung" hier vermerken End Sub ' fill_in_number ' d. Funktion sucht anhand der möglichen einsetzbaren Ziffern nach dem Vorkommen von genau einer solchen Möglichkeitsziffer innerhalb von Reihen, Spalten und Arealen - diese MUSS es dann auch sein Function find_only_one_possible() As Long Dim L_number As Long Dim L_single_counter As Long Dim L_sudoku_field_index As Long Dim L_row_counter As Long Dim L_column_counter As Long ' Suchschleifenvariablen anlegen Dim x, y, z As Long ' yyy --- yyy VBA-spezifische Aufrufparameter anlegen (könnte in anderen Programmiersprachen anders sein) Dim L_call_x As Long Dim L_call_y As Long ' --------------------------------------------------------------------------------------------------------------------------------------------- ' Code für Horizontaltest (=die Reihen) auf Einzelvorkommen einer Sudoko-Ziffer, die genau einmal in den ermittelten möglichen Ziffern vorkommt ' --------------------------------------------------------------------------------------------------------------------------------------------- ' xxx --- xxx Anpassen bei Sudoku-Änderung For y = 1 To 73 Step 9 ' 9 Zahlenspeicher für Reihen sind zu bearbeiten ' xxx --- xxx Anpassen bei Sudoku-Änderung For z = 1 To 9 ' jeweils 9 Ziffern kommen darin pro Reihe vor L_single_counter = 0 ' Initialisierung d. Fundzählers - der sollte auf 1 bleiben, ansonsten gibt es keine Einzelmöglichkeit an der jeweiligen Koordinate mit der jeweiligen Ziffer ' xxx --- xxx Anpassen bei Sudoku-Änderung For x = 1 To 9 ' 9 Koordinaten sind zu bearbeiten L_sudoku_field_index = y + x - 1 ' Sudoku-Koordinate anhand Koordinatenformel für Reihen ermitteln If BA_filled_in(L_sudoku_field_index) = True Then ' ist dort schon eine Zahl eingesetzt? GoTo next_loop ' ja -> weiter mit nächster Koordinate (Geschwindigkeitsgründe und Vermeidung, auf alte nicht gelöschte Möglichkeitsziffern zu treffen) End If If LA_possibles(L_sudoku_field_index, z) = C_number_possible Then ' ist die akt. Ziffer dort möglich? L_single_counter = L_single_counter + 1 ' ja -> vermerken If L_single_counter = 1 Then ' kam die Zahl genau einmal vor (=verblieb der Einzelzähler auf 1)? L_found_index_position = L_sudoku_field_index ' ja, gefundene Sudoku-Koordinate in Globalvariable sichern (=vorerst davon ausgehen, dass dies auch weiterhin so bleibt, ansonsten wird die Koordinate sowieso neu überschrieben -> wird aber für Exit Function unten gebraucht, falls der Zähler tatsächlich auf 1 bleiben sollte - das weiß man ja vorher nicht) End If End If next_loop: Next x ' nächste Koordinate bearbeiten If L_single_counter = 1 Then ' wurde die Einsatzmöglichkeit der Ziffer nur einmal gefunden (=blieb der Zähler die ganze Zeit auf 1)? find_only_one_possible = z ' im Funktionscode die gefundene Zahl vermerken - Hauptprogramm: >0 = eine gefundene Lösungsziffer da u. die Zahl >0 ist sie gleich auch Exit Function ' Funktionsende End If Next z ' nächste Zahl bearbeiten Next y ' nächste Reihe bearbeiten ' -------------------------------------------------------------------------------------------------------------------------------------------- ' Code für Vertikaltest (=die Spalten) auf Einzelvorkommen einer Sudoko-Ziffer, die genau einmal in den ermittelten möglichen Ziffern vorkommt ' -------------------------------------------------------------------------------------------------------------------------------------------- ' xxx --- xxx Anpassen bei Sudoku-Änderung For x = 1 To 9 ' 9 Zahlenspeicher für Spalten sind zu bearbeiten ' xxx --- xxx Anpassen bei Sudoku-Änderung For z = 1 To 9 ' jeweils 9 Ziffern kommen darin pro Spalte vor L_single_counter = 0 ' Initialisierung d. Fundzählers - der sollte auf 1 bleiben, ansonsten gibt es keine Einzelmöglichkeit an der jeweiligen Koordinate mit der jeweiligen Ziffer ' xxx --- xxx Anpassen bei Sudoku-Änderung For y = x To x + 72 Step 9 ' Sudoku-Koordinate anhand Koordinatenformel für Spalten ermitteln (hier in der Schleifenschrittweite) L_sudoku_field_index = y ' und nur jeweilige Koordinate ermitteln If BA_filled_in(L_sudoku_field_index) = True Then ' ist dort schon eine Zahl eingesetzt? GoTo next_loop1 ' ja -> weiter mit nächster Koordinate (Geschwindigkeitsgründe und Vermeidung, auf alte nicht gelöschte Möglichkeitsziffern zu treffen) End If If LA_possibles(L_sudoku_field_index, z) = C_number_possible Then ' ist die akt. Ziffer dort möglich? L_single_counter = L_single_counter + 1 ' ja -> vermerken If L_single_counter = 1 Then ' kam die Zahl genau einmal vor (=verblieb der Einzelzähler auf 1)? L_found_index_position = L_sudoku_field_index ' ja, gefundene Sudoku-Koordinate in Globalvariable sichern (=vorerst davon ausgehen, dass dies auch weiterhin so bleibt, ansonsten wird die Koordinate sowieso neu überschrieben -> wird aber für Exit Function unten gebraucht, falls der Zähler tatsächlich auf 1 bleiben sollte - das weiß man ja vorher nicht) End If End If next_loop1: Next y ' nächste Koordinate bearbeiten If L_single_counter = 1 Then ' wurde die Einsatzmöglichkeit der Ziffer nur einmal gefunden (=blieb der Zähler die ganze Zeit auf 1)? find_only_one_possible = z ' im Funktionscode die gefundene Zahl vermerken - Hauptprogramm: >0 = eine gefundene Lösungsziffer da u. die Zahl >0 ist sie gleich auch Exit Function ' Funktionsende End If Next z ' nächste Zahl bearbeiten Next x ' nächste Spalte bearbeiten ' -------------------------------------------------------------------------------------------------------------------------- ' Code für Arealtest auf Einzelvorkommen einer Sudoko-Ziffer, die genau einmal in den ermittelten möglichen Ziffern vorkommt ' -------------------------------------------------------------------------------------------------------------------------- ' xxx --- xxx Anpassen bei Sudoku-Änderung For x = 1 To 9 ' 9 Zahlenspeicher für Areale sind zu bearbeiten ' xxx --- xxx Anpassen bei Sudoku-Änderung For y = 1 To 9 ' jeweils 9 Ziffern kommen darin pro Areal vor L_single_counter = 0 ' Initialisierung d. Fundzählers - der sollte auf 1 bleiben, ansonsten gibt es keine Einzelmöglichkeit an der jeweiligen Koordinate mit der jeweiligen Ziffer For z = 1 To 9 ' 9 möglichen Ziffern / Zifferkoordinate abgrasen L_call_x = x ' Aufrufparameter für Zahlenspeicher übergeben L_call_y = z ' Aufrufparameter für Ziffer selbst übergeben L_sudoku_field_index = get_sudoku_field_index(C_areas, L_call_x, L_call_y) ' hier die spezielle Arealberechnungsformel für die Ermittlung auf die entsprechende Sudoku-Koordinate der akt. durchlaufenen Arealziffer im jeweiligen Areal nutzen If BA_filled_in(L_sudoku_field_index) = True Then ' ist dort schon eine Zahl eingesetzt? GoTo next_loop2 ' ja -> weiter mit nächster Koordinate (Geschwindigkeitsgründe und Vermeidung, auf alte nicht gelöschte Möglichkeitsziffern zu treffen) End If If LA_possibles(L_sudoku_field_index, y) = C_number_possible Then ' ist die akt. Ziffer dort möglich? L_single_counter = L_single_counter + 1 ' ja -> vermerken If L_single_counter = 1 Then ' kam die Zahl genau einmal vor (=verblieb der Einzelzähler auf 1)? L_found_index_position = L_sudoku_field_index ' ja, gefundene Sudoku-Koordinate in Globalvariable sichern (=vorerst davon ausgehen, dass dies auch weiterhin so bleibt, ansonsten wird die Koordinate sowieso neu überschrieben -> wird aber für Exit Function unten gebraucht, falls der Zähler tatsächlich auf 1 bleiben sollte - das weiß man ja vorher nicht) End If End If next_loop2: Next z ' nächste Koordinate bearbeiten If L_single_counter = 1 Then ' wurde die Einsatzmöglichkeit der Ziffer nur einmal gefunden (=blieb der Zähler die ganze Zeit auf 1)? find_only_one_possible = y ' im Funktionscode die gefundene Zahl vermerken - Hauptprogramm: >0 = eine gefundene Lösungsziffer da u. die Zahl >0 ist sie gleich auch Exit Function ' Funktionsende End If Next y ' nächste Zahl bearbeiten Next x ' nächstes Areal bearbeiten find_only_one_possible = False ' falls Funktion bis hier durchgelaufen ist, wurde nichts gefunden -> melden End Function ' find_only_one_possible ' d. Funktion sucht aus den vorhandenen Möglichkeit für das jeweiligen Sudoku-Feld eine einsetzbare Spekulativzahl (für den Backtracing-Algorithmus, der damit dann weiter zu lösen versucht) Function find_backtracing_possible(L_sudoku_field_index, L_possible_index As Long) As Long Dim x As Long ' Suchschleifenindex For x = L_possible_index To 9 ' ab vom Backtracing-Algorithmus übergebener Ziffer suchen, ob es allenfalls noch verbleibende mögliche Ziffern im aktuellen Sudoku-Feld gibt If LA_possibles(L_sudoku_field_index, x) <> 0 Then ' gibt es eine? find_backtracing_possible = x ' ja, dann diese übergeben Exit Function ' Funktion beeendet End If Next x ' ansonsten weitermachen bis etwas gefunden wird find_backtracing_possible = 0 ' oder die Suchschleife keine mögliche Ziffer mehr auf der jeweiligen Sudoku-Koordinate anbieten kann, dann wird 0 geliefert, damit der Backtracing-Algorithmus weiß, die nächsten freien Sudoku-Koordinate zu übermitteln (mit find_empty_sudoku_field_index kriegt er die... und Index 1 muss natürlich zwecks neuer Suche nach Spekulativzahlen an der so gefundenen Sudoku-Koordinate übermittelt werden) End Function ' d. Funktion sucht nach einem noch nicht ausgefüllten Sudoku-Feld (für den Backtracing-Algorithmus) Function find_empty_sudoku_field_index(L_start_index As Long) As Long Dim x As Long ' Suchschleifenindex For x = L_start_index To 81 ' max. 81 Sudoku-Felder sind abzugrasen If BA_filled_in(x) = False Then ' fehlt an einer Sudoku-Koordinate noch eine Ziffer? find_empty_sudoku_field_index = x ' ja, dann diese Koordinate übergeben Exit Function ' Funktion beeendet End If Next x ' ansonsten weitermachen bis etwas gefunden wird ' yyy --- yyy guter Programmierstil - handelt sich aber um toten Code, da sonst die Funktion (=noch nicht ausgefüllte Zahl auf der Sudoku-Koordinate nicht aufgerufen werden würde - Sudoku wäre bereits gelöst!) find_empty_sudoku_field_index = 0 ' hat die Suchschleife keine freie Stelle mehr im Sudoku gefunden, wird 0 geliefert (kommt aber nicht vor!) End Function ' d. Sub löscht sowohl das Sudoku-Angaben- als auch das Sudoku-Lösungs-Feld Private Sub empty_given_and_solution_field() Call empty_solution_field Call empty_given_sudoku_field End Sub ' d. Sub löscht das Sudoku-Lösungsfeld ' xxx --- xxx Anpassen bei Transpilation in andere Sprachen / Systeme (Ein- / Ausgaberoutine) ' yyy --- yyy Optimierung mit Übergabekoordinaten (was wo gelöscht werden soll -> zahlt sich auch bei Anlegen / Löschen zu speichernder Sudokus aus - braucht aber Aufrufmakro für Benutzer dann)) Private Sub empty_solution_field() ActiveSheet.Unprotect ' Arbeitsblatt entsperren für Löscharbeiten NUR IN EXCEL (xxx --- xxx) Application.ScreenUpdating = False ' Bildschirmaktualisierung (Flackerung bei Makros) verhindern bei Auswahl / Löschung Range(Cells(2, 2), Cells(10, 10)).Select ' Excel-spezifische Ansteuerung der entsprechenden Sudoku-Lösungszellen Selection.ClearContents ' eigentliche Inhaltslöschung durchführen Selection.Interior.ColorIndex = xlNone ' allfällige Hervorhebungsfarben löschen Call restore_font_to_standard ' Standardschrift und Standardhintergrund der Zellen wieder herstellen (ohne Hervorhebungen im Hintergrund) Range("B11:J11").Select ' Statuszeile darunter mit Lösungsstandangaben anwählen Selection.ClearContents ' Statuszeile löschen Range(Cells(2, 14), Cells(2, 14)).Select ' Ansteuerung des oberen Ecks im Sudoku-Eingabefeldes Application.ScreenUpdating = True ' Bildschirmaktualisierung wieder zulassen ActiveSheet.Protect ' Arbeitsblatt wieder sperren NUR IN EXCEL (xxx --- xxx) End Sub ' d. Sub löscht das zu lösende Sudoku ' xxx --- xxx Anpassen bei Transpilation in andere Sprachen / Systeme (Ein- / Ausgaberoutine) ' yyy --- yyy Optimierung mit Übergabekoordinaten (was wo gelöscht werden soll -> zahlt sich auch bei Anlegen / Löschen zu speichernder Sudokus aus - braucht aber Aufrufmakro für Benutzer dann) Private Sub empty_given_sudoku_field() ActiveSheet.Unprotect ' Arbeitsblatt entsperren für Löscharbeiten NUR IN EXCEL (xxx --- xxx) Application.ScreenUpdating = False ' Bildschirmaktualisierung (Flackerung bei Makros) verhindern bei Auswahl / Löschung Call empty_highlighted_fields ' gleich alle allfälligen Hervorhebungen illegaler Felder mitlöschen lassen ActiveSheet.Unprotect ' empty_highlighted_fields schützt Arbeitsblatt wieder (kann ja auch über Symbolleiste aufgerufen werden), darum hier wieder entsperren NUR IN EXCEL (xxx --- xxx) Range(Cells(2, 14), Cells(10, 22)).Select ' Excel-spezifische Ansteuerung der entsprechenden Sudoku-Eingabezellen Selection.ClearContents ' eigentliche Inhaltslöschung durchführen Selection.Interior.ColorIndex = xlNone ' allfällige Hervorhebungsfarben löschen Call restore_font_to_standard ' Standardschrift und Standardhintergrund der Zellen wieder herstellen (ohne Hervorhebungen im Hintergrund) Range(Cells(2, 14), Cells(2, 14)).Select ' Ansteuerung des oberen Ecks im Sudoku-Eingabefeldes Application.ScreenUpdating = True ' Bildschirmaktualisierung wieder zulassen ActiveSheet.Protect ' Arbeitsblatt wieder sperren NUR IN EXCEL (xxx --- xxx) End Sub ' d. Sub gibt ein (gelöstes) Sudoku aus ' xxx --- xxx Anpassen bei Transpilation in andere Sprachen / Systeme (Ein- / Ausgaberoutine) Sub write_sudoku(L_start_row, L_start_column As Long) ' Ausgabeschleifvarialben Dim x As Long Dim y As Long Application.ScreenUpdating = False ' Bildschirmaktualisierung (Flackerung bei Makros) verhindern bei Auswahl / Löschung ' Initialisierung der Startzeile / -spalte vornehmen für Ausgabeschleife vornehmen L_start_row = L_start_row - 1 L_start_column = L_start_column - 1 For x = 1 To 9 ' 9 Zeilen / Spalten For y = 1 To 9 ' zu je 9 Elementen darin (=die Sudoku-Ziffern) If LA_rows(x, y) <> 0 Then ' 0-Ausgbe vermeiden, falls Sudoku nicht ganz gelöst werden konnte Cells(L_start_row + x, L_start_column + y) = LA_rows(x, y) ' es reicht, die Zeilen auszugeben - Spalten und Areale beinhalten im fertig gelösten Sudoku logischerweise die gleichen Ziffern, aber die haben ja bloß der Sudoko-Berechnung gedienten und sind für Doppel- bzw. Dreifachausgabe überflüssig End If Next y Next x Application.ScreenUpdating = True ' Bildschirmaktualisierung wieder zulassen End Sub ' d. Funktion versucht das Sudoku ausschließlich anhand der Logik zu lösen (=Einzelvorkommensuche aus den möglichen Ziffern bzw. Einzelvorkommen aus den Möglichkeiten in Reihen, Spalten oder Arealen) Function try_logic_solve() As Boolean Dim L_try_counter As Long Dim L_found_number As Long For L_try_counter = L_filled_in_counter To 81 ' Lösungssuchschleife initialisieren (Differenz aus den 81 Sudoku-Feldern abzüglich der bereits vorausgefüllten davon) L_found_number = generate_possibles ' die einsetzbare Ziffern beschaffen lassen (falls sofort eine eindeutige Möglichkeit gefunden wird, steht die "Sofortziffer" im Funktionsergebnis) If L_found_number > 0 Then ' wurde tatsächlich schon eine Einzelmöglichkeit gefunden? Call fill_in_number(L_found_index_position, L_found_number) ' ja -> dann spricht nichts dagegen, die gefundene Ziffer einzusetzen (in der Globalvariable L_found_index_position steht nach Beschaffung der möglichen Ziffern die Indexposition für die gefundene Ziffer) ' ddd --- ddd Debug-Code ' Call empty_solution_field ' Lösungsfeld löschen ' Call write_sudoku(2, 2) ' momentanen Sudoku-Lösungsstand testweise ausgeben GoTo next_loop ' gleich nächsten Schleifendurchlauf starten, um sich frische Möglichkeiten bzw. eine Sofortziffer geben zu lassen (falls abermals möglich) End If L_found_number = find_only_one_possible ' noch keine Sofortziffer bei der Möglichkeitssuche gefunden heißt nächste Funktion bemühen: Reihen u. Spalten der möglichen einsetzbaren Ziffern nach Einzelvorkommen von genau einer einzigen (alleinstehenden) Ziffer abgrasen If L_found_number > 0 Then ' spätestens jetzt sollte etwas gefunden sein - wurde es gefunden? Call fill_in_number(L_found_index_position, L_found_number) ' gefunden -> einsetzen ' ddd --- ddd Debug-Code ' Call empty_solution_field ' Lösungsfeld löschen ' Call write_sudoku(2, 2) ' momentanen Sudoku-Lösungsstand testweise ausgeben Else Exit For ' nicht gefunden -> Ende der Suche - Sudoku im gegebenen Zustand nicht ausschließlich anhand der Logik lösbar, Lösungsschleifenversuchsende End If next_loop: Next L_try_counter If L_filled_in_counter = 81 Then ' sind alle 81 Sudoku-Felder ausgefüllt, d.h. war die Lösungsschleife erfolgreich? try_logic_solve = True ' ja, positive Lösung melden Else try_logic_solve = False ' nein, dann wurde die Lösungsschleife vorzeitig beendet -> Negativ melden, damit Hauptprogramm "härtere Geschütze" auffährt, d.h auf Backtracingmodus geht End If End Function ' try_logic_solve ' d. Funktion setzt eine Ziffer spekulativ ins Sudoku ein - das kommt beim Backtracing zur Anwendung, wenn Logik allein nicht zum Ziel führt und mehrere Möglichkeiten existieren, wie es weitergehen könnte Function try_fill_in(L_try_start_field, L_try_number As Long) As Boolean ' yyy --- yyy in anderen Programmiersprachen wäre es möglich, hier direkt die der Routine selbst übergebene Variable zu verwenden Dim L_call_start_index As Long ' Aufruf der Suchroutine vorbereiten (reine Übergabevariable) L_call_start_index = L_try_start_field ' Start-Sudoku-Koordinate übergeben für Aufruf der Suchroutine, wo es noch weitere freie Sudoku-Felder gibt L_empty_field = find_empty_sudoku_field_index(L_call_start_index) ' Suche nach dem erstbesten noch nicht ausgefüllten Feld ab dem übergebenen durchführen L_last_tried_number = find_backtracing_possible(L_empty_field, L_try_number) ' Suche nach erstbester (niedrigster) passender Ziffer auf dem so gefundenen Feld If L_last_tried_number > 0 Then ' wurde tatsächlich eine noch nicht versuchte Ziffer dort gefunden Call fill_in_number(L_empty_field, L_last_tried_number) ' ja, dann testweises Probeeinfügen dieser gefundenen Ziffer durchführen try_fill_in = True ' Positiv melden, damit Hauptprogramm mit dieser Spekulativziffer weiter anhand der Logik zu lösen versucht Else try_fill_in = False ' wenn nichts gefunden wurde auch das dem Hauptprogramm melden, damit allenfalls die Unlösbarkeitsmeldung ausgegeben wird oder eine neue Startkoordinate übermittelt wird, wo neu nach Spekulativziffern gesucht werden soll (normalerweise um eins höher) End If End Function ' try_fill_in ' d. Sub stellt d. Standardschrift und d. Standardhintergrund der Zellen wieder her (ohne Hervorhebungen im Hintergrund) Private Sub restore_font_to_standard() With Selection.Font .Name = "Arial" .Size = 20 .Strikethrough = False .Superscript = False .Subscript = False .OutlineFont = False .Shadow = False .Underline = xlUnderlineStyleNone .ColorIndex = xlAutomatic .Bold = True End With End Sub ' d. Sub setzt die Hervorhebungsfarbe für illegale Sudoku-Felder Private Sub set_illegal_color() ActiveSheet.Unprotect ' Arbeitsblatt entsperren für Operationen NUR IN EXCEL (xxx --- xxx) With Selection.Interior ' Zelle einfärben .ColorIndex = 45 ' mit gewünschtem eingestellten Farbcode .Pattern = xlSolid ' flächige Vollfarbe verwenden End With Call restore_font_to_standard ' Standardschrift ebenfalls wieder herstellen ActiveSheet.Protect ' Arbeitsblatt wieder sperren NUR IN EXCEL (xxx --- xxx) End Sub ' d. Sub hebt gefundene sudoku-regelwidrige Duplikate hervor, damit diese vor dem Lösungsversuch vom Benutzer bereinigt werden können (und müssen!) Private Sub highlight_illegals(L_row, L_column As Long) Const C_rows = 1 Dim L_start_column As Long Dim L_start_row As Long Dim L_sudoku_coordinate As Long ' Ausgabeschleifvarialben Dim x As Long Dim y As Long ' Initialisierung der Startzeile / -spalte für Ausgabeschleife vornehmen L_start_row = L_row L_start_column = L_column For x = 1 To 9 ' 9 Zeilen (Spalten u. Areala können unterbleiben, da alles auch in den Zeilen vorkommt = eine Ausgaberoutine u. keine Berechnungsroutine) For y = 1 To 9 ' zu je 9 Elementen darin (=die Sudoku-Ziffern) L_sudoku_coordinate = get_sudoku_field_index(C_rows, x, y) ' korrespondierende Sudoku-Koordinate holen If LA_rows(x, y) = 0 And BA_non_numeric_field(L_sudoku_coordinate) = True Then ' Wert 0 (VBA Buchstaben in Long eingelesen = 0 -> soll ignoriert werden, da sonst alle unausgefüllten Felder illegal wären) und liegen dort tatsächlich auch ein oder mehrere Buchstaben vor? Range(Cells(L_start_row + x, L_start_column + y), Cells(L_start_row + x, L_start_column + y)).Select ' ja, Zelle für Illegal-Hervorhebung anwählen set_illegal_color ' Zelle als illegal markieren GoTo next_loop ' weitermachen mit nächster Sudoku-Koordinate End If If BA_illegal_sudoku_field(L_sudoku_coordinate) = True Then ' handelt es sich um ein sonstiges illegales (regelwidriges) Sudoku-Feld (Duplikat oder zu hoher / niedriger Ziffern-Wert)? Range(Cells(L_start_row + x, L_start_column + y), Cells(L_start_row + x, L_start_column + y)).Select ' ja, Zelle für Illegal-Hervorhebung anwählen set_illegal_color ' Zelle als illegal markieren End If next_loop: Next y ' nächstes Element (Index) Next x ' nächste Zeile End Sub ' highlight_illegals ' d. Sub löscht hervorgehobene Felder im Angaben- u. Lösungsfeld des Sudokus Private Sub empty_highlighted_fields() ActiveSheet.Unprotect ' Arbeitsblatt entsperren für Löscharbeiten NUR IN EXCEL (xxx --- xxx) Application.ScreenUpdating = False ' Bildschirmaktualisierung (Flackerung bei Makros) verhindern bei Auswahl / Löschung ' yyy --- yyy Variablen verwenden Range(Cells(2, 14), Cells(10, 22)).Select ' Excel-spezifische Ansteuerung der entsprechenden Sudoku-Eingabezellen Selection.Interior.ColorIndex = xlNone ' allfällige Hervorhebungsfarben löschen Call restore_font_to_standard ' Standardschrift und Standardhintergrund der Zellen wieder herstellen (ohne Hervorhebungen im Hintergrund) Range(Cells(2, 2), Cells(10, 10)).Select ' Excel-spezifische Ansteuerung der entsprechenden Sudoku-Eingabezellen Selection.Interior.ColorIndex = xlNone ' allfällige Hervorhebungsfarben löschen Call restore_font_to_standard ' Standardschrift und Standardhintergrund der Zellen wieder herstellen (ohne Hervorhebungen im Hintergrund) Range(Cells(2, 14), Cells(2, 14)).Select ' Ansteuerung des oberen Ecks im Sudoku-Eingabefeldes Application.ScreenUpdating = True ' Bildschirmaktualisierung wieder zulassen ActiveSheet.Protect ' Arbeitsblatt wieder sperren NUR IN EXCEL (xxx --- xxx) End Sub ' d. Sub macht nichts - dient zur Abstandsbildung in der Icon-Leiste v. Sudoku-Muff als Sub zum Aufrufen, denn dort draufgedrückt wird Private Sub dummy_sub() Dim dummy As Long dummy = 0 End Sub ' d. Sub erstellt die Symbolleiste f. die Schnittstellen-Makros zu Sudoku-Muff Public Sub create_sudoku_muff_bar() Dim CB As CommandBar ' benutzerdef. Symbolleiste reservieren Dim CBC As CommandBarButton ' Icon (Symbol) dafür reservieren Dim controlsymbol_name As String ' Name dafür reservieren controlsymbol_name = CS_sudoku_bar_name ' Name anlegen On Error Resume Next ' falls Symbolleiste schon angelegt ist, Fehlermeldung überspringen Application.CommandBars(controlsymbol_name).Delete ' Symbolleiste löschen (damit die nicht x-fach angelegt wird bei Aufruf) On Error GoTo 0 ' Errorhandler wieder löschen Set CB = Application.CommandBars.Add(Name:=controlsymbol_name, temporary:=True, Position:=msoBarTop) ' nur bis Excel-Ende Symbolleiste "drin" lassen CB.Visible = True ' Symbolleiste natürlich anzeigen ' Angabenfeld löschen-Icon anlegen Set CBC = CB.Controls.Add(Type:=msoControlButton) ' neuen Knopf (Symbol) anlegen With CBC .FaceId = 485 ' Code 485 = Rasterpunkte .Caption = "Angabenfeld löschen" ' Infotext f. was "er macht" .OnAction = "empty_given_sudoku_field" ' Makro dem Knopf zuweisen End With ' Lösungsfeld löschen-Icon anlegen Set CBC = CB.Controls.Add(Type:=msoControlButton) ' neuen Knopf (Symbol) anlegen With CBC .FaceId = 638 ' Code 638 = Tabellengitter f. Lösungsfeld löschen .Caption = "Lösungsfeld löschen" ' Infotext f. was "er macht" .OnAction = "empty_solution_field" ' Makro dem Knopf zuweisen End With ' Angaben u. Lösungsfeld löschen-Icon anlegen Set CBC = CB.Controls.Add(Type:=msoControlButton) ' neuen Knopf (Symbol) anlegen With CBC .FaceId = 453 ' Code 453 = 3er-Radiergummi f. Mehrfachlöschung (eben Angaben u. Lösungsfeld) .Caption = "Angaben- und Lösungsfeld löschen" ' Infotext f. was "er macht" .OnAction = "empty_given_and_solution_field" ' Makro dem Knopf zuweisen End With ' Hervorhebungen löschen-Icon anlegen Set CBC = CB.Controls.Add(Type:=msoControlButton) ' neuen Knopf (Symbol) anlegen With CBC .FaceId = 352 ' Code 352 = rote Birne f. hervorgehobene Felder löschen .Caption = "hervorgehobene Felder löschen" ' Infotext f. was "er macht" .OnAction = "empty_highlighted_fields" ' Makro dem Knopf zuweisen End With ' Abstand-Icon anlegen Set CBC = CB.Controls.Add(Type:=msoControlButton) ' neuen Knopf (Symbol) anlegen With CBC .FaceId = 332 ' Code 332 = Platzhalterleerfläche .Caption = "" ' Infotext f. was "er macht" .OnAction = "dummy_sub" ' Makro dem Knopf zuweisen End With ' Sudoku lösen-Icon anlegen Set CBC = CB.Controls.Add(Type:=msoControlButton) ' neuen Knopf (Symbol) anlegen With CBC .FaceId = 625 ' Code 625 = Zauberstab f. Sudoku lösen .Caption = "gegebenes Sudoku lösen" ' Infotext f. was "er macht" .OnAction = "solve_sudoku" ' Makro dem Knopf zuweisen End With ' Abstand-Icon anlegen Set CBC = CB.Controls.Add(Type:=msoControlButton) ' neuen Knopf (Symbol) anlegen With CBC .FaceId = 332 ' Code 332 = Platzhalterleerfläche .Caption = "" ' Infotext f. was "er macht" .OnAction = "dummy_sub" ' Makro dem Knopf zuweisen End With ' Tastaturkommandos-Reset-Icon anlegen Set CBC = CB.Controls.Add(Type:=msoControlButton) ' neuen Knopf (Symbol) anlegen With CBC .FaceId = 64 ' Code 64 = Tastatur f. Tastaturkommandos frisch zuweisen .Caption = "Reset der Tastaturkommandos" ' Infotext f. was "er macht" .OnAction = "set_key_commands" ' Makro dem Knopf zuweisen End With ' Symbolleiste ausblenden-Icon anlegen Set CBC = CB.Controls.Add(Type:=msoControlButton) ' neuen Knopf (Symbol) anlegen With CBC .FaceId = 657 ' Code 657 = Symboleisten f. Symbolleiste ausblenden .Caption = "Symbolleiste entfernen" ' Infotext f. was "er macht" .OnAction = "autoclose_caller" ' Makro dem Knopf zuweisen End With ' Hilfe- u. Info-Icon anlegen Set CBC = CB.Controls.Add(Type:=msoControlButton) ' neuen Knopf (Symbol) anlegen With CBC .FaceId = 345 ' Code 345 = Birne mit Fragezeichen f. Copyright bzw. Hilfe .Caption = "über Sudoku-Muff" ' Infotext f. was "er macht" .OnAction = "display_info_and_copyright" ' Makro dem Knopf zuweisen End With End Sub ' d. Sub weist d. essentiellen Schnittstellen-Makros zu Sudoku-Muff Tastenkombinationen zu Public Sub set_key_commands() Application.OnKey "^A", "empty_given_and_solution_field" Application.OnKey "^B", "create_sudoku_muff_bar" Application.OnKey "^C", "auto_close" Application.OnKey "^G", "empty_given_sudoku_field" Application.OnKey "^H", "empty_highlighted_fields" Application.OnKey "^L", "empty_solution_field" Application.OnKey "^S", "solve_sudoku" End Sub ' d. Sub setzt die Tastenkombinationen und erstellt d. Symbolleiste f. die Rechnung Private Sub auto_open() Call set_key_commands Call create_sudoku_muff_bar End Sub ' d. Sub setzt die Tastenkombinationen und erstellt d. Symbolleiste f. die Rechnung Private Sub auto_close() Dim CB As CommandBar ' benutzerdef. Symbolleiste reservieren Dim CBC As CommandBarButton ' Icon (Symbol) dafür reservieren Dim controlsymbol_name As String ' Name dafür reservieren controlsymbol_name = CS_sudoku_bar_name ' Name anlegen On Error Resume Next ' falls Symbolleiste schon angelegt ist, Fehlermeldung überspringen Application.CommandBars(controlsymbol_name).Delete ' Symbolleiste löschen (damit die nicht x-fach angelegt wird bei Aufruf) On Error GoTo 0 ' Errorhandler wieder löschen End ' Anweisung hier sicherheitshalber, damit ja alle Variablenreste gelöscht werden - für wiederholte Makroaufrufe End Sub ' ------------- ' Hauptprogramm ' ------------- ' xxx --- xxx dies muss void bzw. int main() bei C-Transpilation werden ' d. Sub holt sich ein gegebenes Sudoku, versucht es zu lösen bzw. gibt allenfalls Unlösbarkeits- oder Falschangabenwarnungen aus, sollten doppelte Zahlen in unzulässiger Weise vorkommen - wenn aber eine Lösung gefunden wurde, so wird das gelöste Sudoku ausgegeben Private Sub solve_sudoku() ' xxx --- xxx Meldungen Const CS_illegal_sudoku_fields As String = "Dieses Sudoku ist nicht lösbar, da regelwidrige Angabenfelder entdeckt wurden (hervorgehoben)" Const CS_logic_solve_message As String = "Sudoku erfolgreich durch reine Logik gelöst - dieses Sudoku ist daher menschenwürdig, da es durch reines Ausschlussverfahren der möglichen einsetzbaren Ziffern lösbar ist (d.h., die Schritte 1-3, die neben stehen)!" Const CS_no_sudoku17_present As String = "Es ist kein sog. ""Sudoku 17"" vorhanden - nur Sudokus mit mind. 17 ausgefüllten und ausgewogen verteilten Felder sind eindeutig lösbar! Sie haben bisher: " Const CS_no_sudoku_present As String = "Bitte geben Sie im Angabenfeld ein zu lösendes Sudoku ein!" Const CS_solve_message_with_backtracing As String = "Sudoku erfolgreich durch zwangsweises Einsetzen von Ziffern gelöst (ein Mensch würde hier schon lange zu knacken haben...)" Const CS_surrender_message As String = "Sudoku-Muff kann dieses Sudoku nicht lösen... Bitte senden sie es an die untenstehende E-Mail-Adresse (=samt der ganzen Excel-Datei, wegen der Versionsangabe von Sudoku-Muff) - Vielen Dank!" Const CL_min_given_fields As Long = 17 ' min Anzahl an vorhandenen ausgefüllten Feldern (Sudoku-17 sollten eindeutig lösbar sein, wenn ausgewogen verteilte angaben vorhanden sind) Dim B_last_try_status As Boolean ' wenn eine einsetzbare Ziffer an der jeweiligen Sudoku-Koordinate gefunden wird, ist es positiv, ansonsten negativ Dim B_secflag As Boolean ' Backup-Flag f. Backtracing (damit der momentane Stand nicht wiederhergestellt wird, falls noch Ziffern ermittelt werden können, bis zu einem Widerspruch) Dim L_filled_in_counter_checker As Long ' Prüfzählerstand der ausgefüllten Sudoku-Felder f. Backtracing -> wird was ausgefüllt, dann weiter backtracen und Sudoku NICHT wieder herstellen, bis es eben nicht mehr weiter geht Dim L_last_tried_number_BAK As Long ' Sicherung für akt. durchprobierte Backtracing-Nummer ' yyy --- yyy wer auf aut. Abbruchsicherung verzichten will (muss dann auch auf Ausgabe des Lösungsstandszählers verzichten), kann folgende beiden Variablen streichen Dim L_max_backtrace_counter As Long ' max Anzahl an Backtracing-Versuchen (dient für korrekten Abbruch der Lösungsschleife - sonst bestünde Endlosschleifengefahr) Dim L_runcounter As Long ' akt. Standzähler der Backtracing-Versuche Dim L_try_sudoku_field As Long ' hier wird die jeweilige Sudoku-Koordinate, die es zu überprüfen gilt, gespeichert Dim L_try_sudoku_field_BAK As Long ' Sicherung d. akt. durchprobierten Sudoku-Koordinate ' yyy --- yyy X-Sudoku-Vorbereitung (Koordinaten) ' LA_X1_coord(1) = 1 ' LA_X1_coord(2) = 11 ' LA_X1_coord(3) = 21 ' LA_X1_coord(4) = 31 ' LA_X1_coord(4) = 41 ' LA_X1_coord(6) = 51 ' LA_X1_coord(7) = 61 ' LA_X1_coord(8) = 71 ' LA_X1_coord(9) = 81 ' LA_X2_coord(1) = 9 ' LA_X2_coord(2) = 17 ' LA_X2_coord(3) = 25 ' LA_X2_coord(4) = 33 ' LA_X2_coord(5) = 41 ' LA_X2_coord(6) = 49 ' LA_X2_coord(7) = 57 ' LA_X2_coord(8) = 65 ' LA_X2_coord(9) = 73 ' ----------- ' Eingabeteil ' ----------- ' yyy --- yyy Variablen für Einleseposition Call read_given_sudoku(2, 14) ' gegebenes Sudoku am Beginn erst einmal einlesen Call empty_solution_field ' Platz schaffen für neue Lösung im Lösungsfeld, falls dort noch was stehen sollte (und gleichzeitig Statuszeile löschen, falls Logiklösung allein zum Ziel führt -> dann braucht es keine Backtracing-Versuche) ActiveSheet.Unprotect ' Arbeitsblatt entsperren für Operationen NUR IN EXCEL (xxx --- xxx) ' Test auf gültige Angaben If check_illegals = True Then ' auf illegale Werte prüfen (Duplikate u. unzulässige Eingaben, d.h. zu hohe / niedrige Werte bzw. Buchstaben -> bis auf die Duplikate wurden bereits von read_given_sudoku markiert die illegalen Felder markiert, d.h. die müssen nur noch ausgewertet werden) Call highlight_illegals(1, 13) ' wurden welche gefunden bzw. waren bereits welche markiert? MsgBox (CS_illegal_sudoku_fields) ' ja, dann Warnmeldung ausgeben ActiveSheet.Protect ' Arbeitsblatt wieder sperren NUR IN EXCEL (xxx --- xxx) End ' Programmende (zzz --- zzz: End Sub würde bei 2. Aufruf fehlerhafte Ausführung bedeuten -> Grund: unklar, aber vermutlich werden nicht alle Variablen ordnungsgem. gelöscht, so dass Störreste drinnen bleiben, obwohl überall korr. Initialisierung erfolgt - zumindest konnte kein diesbezüglicher Fehler gefunden werden / oder übersehen?) End If ' Test auf ausgefülltes Sudoku If L_filled_in_counter = 0 Then ' liegt überhaupt eine Eingabe im Angabenfeld vor? MsgBox (CS_no_sudoku_present) ' nein, dann darum bitten ActiveSheet.Protect ' Arbeitsblatt wieder sperren NUR IN EXCEL (xxx --- xxx) End ' Programmende End If ' Test auf mind. 17 Stück eingegebener Ziffern (Sudoku-17 sollten eindeutig lösbar sein, wenn ausgewogen verteilte angaben vorhanden sind) If L_filled_in_counter < CL_min_given_fields Then ' liegt eine "halbwegs lösbares" Sudoku überhaupt vor (17er-Sudoku)? MsgBox (CS_no_sudoku17_present + Str(L_filled_in_counter) + " Felder ausgefüllt") ' nein, dann darum bitten ActiveSheet.Protect ' Arbeitsblatt wieder sperren NUR IN EXCEL (xxx --- xxx) End ' Programmende End If ' ----------------------------------------- ' Verarbeitungs- u. allfälliger Ausgabeteil ' ----------------------------------------- If try_logic_solve = True Then ' Sudoku bereits allein durch reine Logik lösbar gewesen (was wo hinein passt) Call write_sudoku(2, 2) ' ja, Sudoku ausgeben MsgBox (CS_logic_solve_message) ' ja, Lösungsmeldung ausgeben ActiveSheet.Protect ' Arbeitsblatt wieder sperren NUR IN EXCEL (xxx --- xxx) End ' Programmende (zzz --- zzz: End Sub würde bei 2. Aufruf fehlerhafte Ausführung bedeuten -> Grund: unklar, aber vermutlich werden nicht alle Variablen ordnungsgem. gelöscht, so dass Störreste drinnen bleiben, obwohl überall korr. Initialisierung erfolgt - zumindest konnte kein diesbezüglicher Fehler gefunden werden / oder übersehen?) End If ' ddd --- ddd Debug-Code ' Call empty_solution_field ' Lösungsfeld löschen ' Call write_sudoku(2, 2) ' momentanen Lösungsstand in Angabefeld schreiben (als Ausgangsbasis für neuen Lösungsversuch) Call backup_sudoku ' war Sudoku nicht lösbar, dann den alten Lösungsstand sichern (den aus dem Logik-Lösungsversuch) und härtere Geschütze auffahren: den Backtracing-Modus L_last_tried_number = 1 ' Initialisierung des hier verwendeten Backtracing-Algorithums (max. niedrigste mögliche Ziffer = 1 für den Start eines Einsetzungsversuches) ' yyy --- yyy Optimierungsinitialisierung? Gleich erstes freies Feld suchen (INLINE bei C-Transpilation als Geschwindigkeitssteigerung) L_try_sudoku_field = 1 ' erster Start für Freifeldsuche für Zifferneinsetzung bei erster Sudoku-Koordinate ' ddd --- ddd Debug-Code ' Call empty_solution_field ' Lösungsfeld löschen ' Call write_sudoku(2, 2) ' momentanen Lösungsstand in Angabefeld schreiben (als Ausgangsbasis für neuen Lösungsversuch) B_secflag = False ' Initialisierung d. Sicherungsflags (zuerst sichern -> wenn es auf true ist, wird die Sicherung beibehalten) ' yyy --- yyy wer auf die Sicherheit d. aut. Abbruchs nach max. Lösungsversuchsanzahl verzichten kann, kann folgende Programmteile streichen (bringt etwas Geschwindigkeit beim Lösen, man muss dann aber im Unlösbarkeitsfall selbst STRG-Untbr. drücken) L_runcounter = 0 ' xxx --- xxx Anpassen bei Sudoku-Größenänderung L_max_backtrace_counter = (81 - L_filled_in_counter - 1) * (81 - L_filled_in_counter - 1) ' max. Backtracing über d. Anzahl d. Leerkoordinaten abz. einer akt. eingesetzer Versuchsziffer und weil im Extremfall die Lösung bei der letzten freien Koordinate gefunden werden könnte das Quadrat davon ' yyy --- yyy wer nur auf die Ausgabe d. Lösungsversuchsstandes verzichten will, kann lediglich den folgenden Programmteil streichen (der kostet später nämlich d. meiste Geschwindigkeit beim Lösen) ' xxx --- xxx Ausgabekoordinaten anpassen Cells(11, 9) = L_max_backtrace_counter ' max. Anzahl ausgeben - damit Benutzer weiß, ob er nicht vorher selbst "STRG-Untbr." drückt Cells(11, 2) = "Versuch:" Cells(11, 7) = "von max.:" Do ' weitermachen mit Backtracing B_last_try_status = try_fill_in(L_try_sudoku_field, L_last_tried_number) ' Backtracing: immer eine Zifer irgendwo einsetzen probieren, bis eine Lösung gefunden werden kann - eine Ziffer muss ja letzlich irgendwo richtig stehen L_filled_in_counter_checker = L_filled_in_counter ' akt. Zählerstand d. ausgefüllten Sudoku-Felder sichern If B_last_try_status = False Then ' wurde keine einsetzbare Ziffer mehr an der entsprechenden Sudoku-Koordinate gefunden? L_try_sudoku_field = L_empty_field + 1 ' ja, dann weitermachen bei nächster Sudoku-Koordinate ' yyy --- yyy hier gleich testen, ob die noch frei ist, dann muss es die Routine nicht L_last_tried_number = 1 ' Reinitialisierung für neue diese neue Sudoku-Koordinate (es wurde ja "Ziffer" 0 geliefert, also nichts Brauchbares) ' ddd --- ddd Debug-Code ' Call empty_solution_field ' Lösungsfeld löschen ' Call write_sudoku(2, 2) ' momentanen Sudoku-Lösungsstand testweise ausgeben If L_try_sudoku_field > 81 Then ' wurden aber schon alle Sudoku-Koordinaten abgegrast (=ging es sich linear aus beim Backtracen, d.h. d. Reihe nach)? Exit Do ' ja, dann ist keine Lösung mittels dem hier verwendeten Backtracing möglich - Backtracing-Lösungsschleife verlassen End If GoTo continue_do ' weitermachen bei der nächsten Sudoku-Koordinate End If ' ddd --- ddd Debug-Code ' Call empty_solution_field ' Lösungsfeld löschen ' Call write_sudoku(2, 2) ' momentanen Sudoku-Lösungsstand testweise ausgeben If try_logic_solve = True Then ' hat jetzt eine Logiklösung mit der eingesetzten Spekulativziffer funktioniert? ' yyy --- yyy wer auf die Ausgabe d. Lösungsversuchsstandes verzichtet, sollte auch das folgenden If-Konstrukt streichen If L_runcounter = 0 Then ' wurde gleich beim 1. Versuch gelöst, d.h. ist d. Lösungsstandszähler noch initialisiert mit 0, da noch kein alter Lösungsstand wiederhergestellt wurde für die nächste Koordinate? ' xxx --- xxx Ausgabekoordinaten anpassen Cells(11, 4) = 1 ' Ausgabe d. akt. Lösungsversuchsstandes (hier absolute 1) End If Call write_sudoku(2, 2) ' ja, dann die gefundene Lösung in das Lösungsfeld schreiben MsgBox (CS_solve_message_with_backtracing) ' entsprechende Lösungsmeldung ausgeben ActiveSheet.Protect ' Arbeitsblatt wieder sperren NUR IN EXCEL (xxx --- xxx) End ' Programmende (zzz --- zzz: End Sub würde bei 2. Aufruf fehlerhafte Ausführung bedeuten -> Grund: unklar, aber vermutlich werden nicht alle Variablen ordnungsgem. gelöscht, so dass Störreste drinnen bleiben, obwohl überall korr. Initialisierung erfolgt - zumindest konnte kein diesbezüglicher Fehler gefunden werden / oder übersehen?) End If ' ddd --- ddd Debug-Code ' Call empty_solution_field ' Lösungsfeld löschen ' Call write_sudoku(2, 2) ' momentanen Sudoku-Lösungsstand testweise ausgeben L_last_tried_number = L_last_tried_number + 1 ' funktioniert die Logiklösung immer noch nicht, dann mit nächster Sekulativ-Ziffer weitermachen lassen (es wird dann ab dieser gesucht, ob schon diese oder allenfalls andere einsetzbare Ziffern existieren) continue_do: If B_secflag = False Then ' muss zuerst gesichert werden? L_last_tried_number_BAK = L_last_tried_number ' ja, akt. durchprobierte Ziffer sichern L_try_sudoku_field_BAK = L_try_sudoku_field ' ja, akt. durchprobierte Sudoku-Koordinate sichern B_secflag = True ' ab jetzt Sicherung beibehalten, bis das Sudoku entweder gelöst ist, oder keine weiteren Ziffern mehr ermittelbar sind (d.h., ein Sudoku-Widerspruch entdeckt wurde) End If If L_filled_in_counter = L_filled_in_counter_checker Then ' nichts mehr weitergegangen beim "Backtracen" (d.h, es wurden tatsächlich keine gültigen einsetzbaren Ziffern mehr ermittelt -> Widerspruch: Sudoku hängt!!!) ' yyy --- yyy wer auf die Sicherheit d. aut. Abbruchs nach max. Lösungsversuchsanzahl verzichten kann, kann den folgenden Programmteil streichen (bringt etwas Geschwindigkeit beim Lösen, man muss dann aber im Unlösbarkeitsfall selbst STRG-Untbr. drücken) L_runcounter = L_runcounter + 1 ' Lösungsversuchszähler erhöhen If L_runcounter > L_max_backtrace_counter Then ' überschreitet der die max. Leerkoordinatenanzahl? Exit Do ' ja, dann Lösungsschleife verlassen, Sudoku-Muff kann hier nicht weiterhelfen End If ' yyy --- yyy wer auf die Ausgabe d. Lösungsversuchsstandes verzichten kann, kann den folgenden Programmteil streichen (der kostet nämlich d. meiste Geschwindigkeit beim Lösen) ' xxx --- xxx Ausgabekoordinaten anpassen Cells(11, 4) = L_runcounter ' Ausgabe d. akt. Lösungsversuchsstandes Call restore_sudoku ' den alten (ersten) Logiklösungsstand wiederherstellen (der stimmt sicher bis dahin), da akt. Backtracing-Lösungsversuch gescheitert ist L_last_tried_number = L_last_tried_number_BAK ' gesicherte, akt. durchprobierte Ziffer ab diesem Zeitpunkt wieder herstellen L_try_sudoku_field = L_try_sudoku_field_BAK ' gesicherte, akt. durchprobierte Sudoku-Koordinate ab diesem Zeitpunkt wieder herstellen B_secflag = False ' für weiteres "Backtracen" an anderen Koordinaten (bis man eine Lösung findet) wieder eine Sicherung ab dort zulassen End If Loop ' weitermachen: nächste möglicher Ziffer einsetzen probieren ' -------------------------------------------------------------------------------------------------------------------------------------------------------- ' Ausgabeteil, der hoffentlich nicht HIER zum Tragen kommt (für Kapitulationsmeldung, ansonsten wäre ja schon an anderer Stelle im Programm gelöst worden) ' -------------------------------------------------------------------------------------------------------------------------------------------------------- ' yyy --- yyy Variablen für Ausgabeposition Call write_sudoku(2, 2) ' momentanen Lösungsstand in Lösungsfeld schreiben (Sudoku konnte leider nicht gelöst werden, wenn Programm bis hierher lief) MsgBox (CS_surrender_message) ' Kapitulationsmeldung ausgeben (sollte normalerweise in 95% aller "lösbaren sog. 17er-Sudokus", möglicherweise 99% nicht auftreten) ActiveSheet.Protect ' Arbeitsblatt wieder sperren NUR IN EXCEL (xxx --- xxx) End ' Anweisung hier sicherheitshalber, damit ja alle Variablenreste gelöscht werden - für wiederholte Makroaufrufe End Sub ' solve_sudokuzurück |