Sudoku z Pythonem
001: """Sudoku, autor: Al Sweigart, al@inventwithpython.com
002: Klasyczna układanka liczbowa z planszą 9x9.
003: Więcej informacji na stronie https://pl.wikipedia.org/wiki/Sudoku.
004: Kod pobrany ze strony https://ftp.helion.pl/przyklady/wiksma.zip.
005: Etykiety: długi, gra, zorientowany obiektowo, łamigłówka"""
006:
007: import copy, random, sys
008:
009: # Ta gra wymaga pliku sudokupuzzle.txt, który zawiera łamigłówkę.
010: # Plik możesz pobrać ze strony https://ftp.helion.pl/przyklady/wiksma.zip.
011: # Oto przykład zawartości takiego pliku:
012: # ..3.2.6..9..3.5..1..18.64....81.29..7.......8..67.82....26.95..8..2.3..9..5.1.3..
013: # 2...8.3...6..7..84.3.5..2.9...1.54.8.........4.27.6...3.1..7.4.72..4..6...4.1...3
014: # ......9.7...42.18....7.5.261..9.4....5.....4....5.7..992.1.8....34.59...5.7......
015: # .3..5..4...8.1.5..46.....12.7.5.2.8....6.3....4.1.9.3.25.....98..1.2.6...8..6..2.
016:
017: # Deklaracja stałych:
018: EMPTY_SPACE = '.'
019: GRID_LENGTH = 9
020: BOX_LENGTH = 3
021: FULL_GRID_SIZE = GRID_LENGTH * GRID_LENGTH
022:
023:
024: class SudokuGrid:
025: def __init__(self, originalSetup):
026: # Zmienna originalSetup to łańcuch 81 znaków początkowego stanu
027: # planszy, z liczbami i kropkami (dla pustych pól).
028: # Patrz plik sudokupuzzles.txt
029: self.originalSetup = originalSetup
030:
031: # Dany stan planszy sudoku jest przedstawiony za pomocą słownika
032: # z kluczami (x, y) i wartością liczbową (zapisaną w formie łańcucha znaków) w
033: # przypisanym do klucza miejscu.
034: self.grid = {}
035: self.resetGrid() # Przywróć planszę do stanu początkowego.
036: self.moves = [] # Zapisuje każdy ruch, co jest przydatne podczas korzystania z funkcji Cofnij.
037:
038: def resetGrid(self):
039: """Przywraca początkowy stan planszy,
040: zapisany w self.originalSetup."""
041: for x in range(1, GRID_LENGTH + 1):
042: for y in range(1, GRID_LENGTH + 1):
043: self.grid[(x, y)] = EMPTY_SPACE
044:
045: assert len(self.originalSetup) == FULL_GRID_SIZE
046: i = 0 # i rośnie od 0 do 80
047: y = 0 # y rośnie od 0 do 8
048: while i < FULL_GRID_SIZE:
049: for x in range(GRID_LENGTH):
050: self.grid[(x, y)] = self.originalSetup[i]
051: i += 1
052: y += 1
053:
054: def makeMove(self, column, row, number):
055: """Umieszcza na planszy liczbę w danej kolumnie (litera od A do I)
056: i danym wierszu (liczba całkowita od 1 do 9)."""
057: x = 'ABCDEFGHI'.find(column) # Zamień na liczbę całkowitą.
058: y = int(row) - 1
059:
060: # Sprawdź, czy dany ruch jest wykonywany na liczbie, która jest podana na początku gry:
061: if self.originalSetup[y * GRID_LENGTH + x] != EMPTY_SPACE:
062: return False
063:
064: self.grid[(x, y)] = number # Umieść tę liczbę na siatce.
065:
066: # Musimy zapisać osobną kopię słownika:
067: self.moves.append(copy.copy(self.grid))
068: return True
069:
070: def undo(self):
071: """Przywróć stan planszy do poprzedniego stanu
072: zapisanego w liście self.moves."""
073: if self.moves == []:
074: return # Brak danych w self.moves, nic nie rób.
075:
076: self.moves.pop() # Usuń dane bieżącego stanu.
077:
078: if self.moves == []:
079: self.resetGrid()
080: else:
081: # Ustaw poprzedni stan siatki:
082: self.grid = copy.copy(self.moves[-1])
083:
084: def display(self):
085: """Wyświetl aktualny stan planszy na ekranie."""
086: print(' A B C D E F G H I') # Wyświetl oznaczenia kolumn.
087: for y in range(GRID_LENGTH):
088: for x in range(GRID_LENGTH):
089: if x == 0:
090: # Wyświetl oznaczenia wierszy:
091: print(str(y + 1) + ' ', end='')
092:
093: print(self.grid[(x, y)] + ' ', end='')
094: if x == 2 or x == 5:
095: # Wyświetl linię pionową:
096: print('| ', end='')
097: print() # Wyświetl znak nowej linii:
098:
099: if y == 2 or y == 5:
100: # Wyświetl linię poziomą:
101: print(' ------+-------+------')
102:
103: def _isCompleteSetOfNumbers(self, numbers):
104: """Zwraca wartość True, jeśli liczby zawierają cyfry od 1 do 9."""
105: return sorted(numbers) == list('123456789')
106:
107: def isSolved(self):
108: """Zwraca wartość True, jeśli bieżący stan planszy to rozwiązanie."""
109: # Sprawdź każdy wiersz:
110: for row in range(GRID_LENGTH):
111: rowNumbers = []
112: for x in range(GRID_LENGTH):
113: number = self.grid[(x, row)]
114: rowNumbers.append(number)
115: if not self._isCompleteSetOfNumbers(rowNumbers):
116: return False
117:
118: # Sprawdź każdą kolumnę:
119: for column in range(GRID_LENGTH):
120: columnNumbers = []
121: for y in range(GRID_LENGTH):
122: number = self.grid[(column, y)]
123: columnNumbers.append(number)
124: if not self._isCompleteSetOfNumbers(columnNumbers):
125: return False
126:
127: # Sprawdź każdy blok 3x3:
128: for boxx in (0, 3, 6):
129: for boxy in (0, 3, 6):
130: boxNumbers = []
131: for x in range(BOX_LENGTH):
132: for y in range(BOX_LENGTH):
133: number = self.grid[(boxx + x, boxy + y)]
134: boxNumbers.append(number)
135: if not self._isCompleteSetOfNumbers(boxNumbers):
136: return False
137:
138: return True
139:
140:
141: print('''Sudoku, autor: Al Sweigart, al@inventwithpython.com
142:
143: Sudoku to układanka liczbowa. Plansza sudoku to siatka 9x9.
144: Spróbuj umieścić na siatce liczby w taki sposób, by dana cyfra od 1 do 9
145: występowała tylko raz w danym wierszu, kolumnie i bloku 3x3.
146:
147: Oto przykładowa plansza na początku gry i już rozwiązana:
148:
149: 5 3 . | . 7 . | . . . 5 3 4 | 6 7 8 | 9 1 2
150: 6 . . | 1 9 5 | . . . 6 7 2 | 1 9 5 | 3 4 8
151: . 9 8 | . . . | . 6 . 1 9 8 | 3 4 2 | 5 6 7
152: ------+-------+------ ------+-------+------
153: 8 . . | . 6 . | . . 3 8 5 9 | 7 6 1 | 4 2 3
154: 4 . . | 8 . 3 | . . 1 --> 4 2 6 | 8 5 3 | 7 9 1
155: 7 . . | . 2 . | . . 6 7 1 3 | 9 2 4 | 8 5 6
156: ------+-------+------ ------+-------+------
157: . 6 . | . . . | 2 8 . 9 6 1 | 5 3 7 | 2 8 4
158: . . . | 4 1 9 | . . 5 2 8 7 | 4 1 9 | 6 3 5
159: . . . | . 8 . | . 7 9 3 4 5 | 2 8 6 | 1 7 9
160: ''')
161: input('Naciśnij Enter, aby rozpocząć...')
162:
163:
164: # Wgraj plik sudokupuzzles.txt:
165: with open('sudokupuzzles.txt') as puzzleFile:
166: puzzles = puzzleFile.readlines()
167:
168: # Usuń znak nowej linii na końcu każdej łamigłówki:
169: for i, puzzle in enumerate(puzzles):
170: puzzles[i] = puzzle.strip()
171:
172: grid = SudokuGrid(random.choice(puzzles))
173:
174: while True: # Główna pętla gry.
175: grid.display()
176:
177: # Sprawdź, czy sudoku zostało rozwiązane.
178: if grid.isSolved():
179: print('Gratulacje! Rozwiązałeś łamigłówkę!')
180: print('Dziękujemy za grę!')
181: sys.exit()
182:
183: # Pobierz ruch gracza:
184: while True: # Pytaj, dopóki gracz nie poda prawidłowego działania.
185: print() # Wyświetl znak nowej linii.
186: print('Podaj ruch lub wpisz RESET, NOWA, COFNIJ, POCZĄTEK lub KONIEC:')
187: print('(Przykładowy ruch to "B4 9".)')
188:
189: action = input('> ').upper().strip()
190:
191: if len(action) > 0 and action[0] in ('R', 'N', 'C', 'P', 'K'):
192: # Gracz podał prawidłowe działanie.
193: break
194:
195: if len(action.split()) == 2:
196: space, number = action.split()
197: if len(space) != 2:
198: continue
199:
200: column, row = space
201: if column not in list('ABCDEFGHI'):
202: print('Nie ma takiej kolumny', column)
203: continue
204: if not row.isdecimal() or not (1 <= int(row) <= 9):
205: print('Nie ma takiego wiersza', row)
206: continue
207: if not (1 <= int(number) <= 9):
208: print('Wybierz cyfrę od 1 do 9, a nie ', number)
209: continue
210: break # Gracz podał odpowiedni ruch.
211:
212: print() # Wyświetl znak nowej linii.
213:
214: if action.startswith('R'):
215: # Przywróć planszę do stanu początkowego:
216: grid.resetGrid()
217: continue
218:
219: if action.startswith('N'):
220: # Wgraj nową łamigłówkę:
221: grid = SudokuGrid(random.choice(puzzles))
222: continue
223:
224: if action.startswith('C'):
225: # Cofnij ostatni ruch:
226: grid.undo()
227: continue
228:
229: if action.startswith('P'):
230: # Pokaż początkowy stan planszy:
231: originalGrid = SudokuGrid(grid.originalSetup)
232: print('Na początku plansza wyglądała tak:')
233: originalGrid.display()
234: input('Naciśnij Enter, aby rozpocząć...')
235:
236: if action.startswith('K'):
237: # Wyjdź z gry.
238: print('Dziękujemy za grę!')
239: sys.exit()
240:
241: # Wykonaj podany przez gracza ruch:
242: if grid.makeMove(column, row, number) == False:
243: print('Nie możesz nadpisać liczby, która była wpisana już na początku gry.')
244: print('Wpisz POCZĄTEK, by zobaczyć planszę początkową.')
245: input('Naciśnij Enter, aby rozpocząć...')