Αλγόριθμος κατασκευής SudoKu
Όταν ο Dr.Kameleon έγραφε, η μέρα ήτο 15/7/2009 και η ώρα 17:42
Ο λόγος για το πολύ διάσημο παιχνιδάκι λογικής…
Αν προσωπικά μ’ αρέσουν τα SudoKu? Από λίγο έως καθόλου.
Καθότι όμως η λογική κατασκευής τους είναι ένα… προγραμματιστικό challenge (btw, εντάσσεται στα NP-Complete problems) κι απ’ όσο διαπίστωσα ψάχνοντας στο internet δεν υπάρχει μεγάλη διαθεσιμότητα πληροφορίας όσον αφορά την κατασκευή τους κατά τρόπο αυτοματοποιημένο, αποφάσισα να ασχοληθώ και να εξηγήσω έναν πρόχειρο-αλλά-αποτελεσματικό αλγόριθμο (βασισμένο σε bruteforce και randomization) στον οποίον κατέληξα, ο οποίος και θα είναι η βάση για τα SudoKu που θα δημοσιευθούν στη Φωλιά.
Τι είναι το Sudoku ?
(Στην διαδεμόνη του μορφή) είναι ένα τετράγωνο 9×9, δηλαδή με συνολικά 81 κουτάκια (που σχηματίζουν παράλληλα 9 μεγάλα τετράγωνα) καθένα εκ των οποίων περιέχει έναν αριθμό από το 1 μέχρι το 9
Οι 3 “νόμοι” του Sudoku
- Κάθε γραμμή του τετραγώνου πρέπει να περιέχει κάθε ψηφίο ΜΟΝΟ μία φορά.
- Κάθε στήλη του τετραγώνου πρέπει να περιέχει κάθε ψηφίο ΜΟΝΟ μία φορά.
- Καθένα από τα 9 μεγάλα τετράγωνα πρέπει να περιέχει κάθε ψηφίο ΜΟΝΟ μία φορά.
Πώς παίζεται ?
Στην τελική του μορφή, τα περισσότερα ψηφία είναι “κρυμμένα”. Επομένως, για να “λυθεί” το SudoKu, ο παίκτης – με λογικούς συνειρμούς και όχι με “μαντεψιές”, χρησιμοποιώντας τους 3 παραπάνω κανόνες – πρέπει να συμπληρώσει τα κενά.

Η λογική κατασκευής
Για να κατασκευάσουμε “αυτόματα” ένα Sudoku, σε αδρές γραμμές, πρέπει :
- Να δημιουργήσουμε έναν τετράγωνο 9×9
- Να συμπληρώσουμε όλα τα κουτάκια του με ψηφία – καθένα εκ των οποίων πληρεί τους παραπάνω τρείς κανόνες.
- Να “κρύψουμε” κάποια από τα ψηφία, έτσι ώστε ο παίκτης να μπορεί να “λύσει” το Sudoku – δηλαδή να συμπληρώσει αυτά που λείπουν
Αναλύοντας τα βήματα
[Προειδοποιώ ότι ο αλγόριθμος αυτός δεν είναι ό,τι-πιο-έξυπνο-μπορεί-να-κυκλοφορήσει - αλλά επειδή η προγραμματιστική μου τακτική ήταν πάντα "πρώτα βλέπουμε αποτέλεσμα και μετά καλλωπίζουμε", here it is!]
- Δημιουργούμε ένα πίνακα
table9×9 – και κάνουμε initialization με την τιμή0(όπου0σημαίνει ότι το κουτάκι δεν έχει συμπληρωθεί) - Δημιουργούμε ένα πίνακα
move81 στοιχείων (στην php, ελλείψει structs, δημιούργησα 3 ξεχωριστούς πίνακες), όπου θα αποθηκευτούν οι “κινήσεις” μας, για κάθε τοποθέτηση ψηφίου στοtable -
Όσο υπάρχουν κενα κουτάκια :
- Εντοπίζουμε τυχαία ένα κενό κουτάκι, με συντεταγμένες (xrand, yrand).
- “Δοκιμάζουμε” στο (xrand, yrand) τυχαία κάποιο από τα 9 ψηφία, έστω
num(μέχρι Ή κάποιο να “ταιριάζει” στη συγκεκριμμένη θέση ΕΙΤΕ να έχουμε δοκιμάσει και τα 9 ψηφία) - Αν ο
numπληρεί για τη θέσηtable[xrand][yrand]τους 3 κανόνες (τους οποίους ελέγχουμε με την συνάρτησηOK), τότε είσαγουμε τονnumστοtable[xrand][yrand]και αποθηκεύουμε την “κίνηση” στον ψευδό-πίνακαmove - Αν έχουμε δοκιμάσει όλα τα ψηφία χωρίς αποτέλεσμα, για τη θέση
table[xrand][yrand], τότε επιλέγουμε τυχαία μια προηγηθείσα “κίνηση” από τον πίνακαmoveκαι την “αναιρούμε” όριζοντας την θέση του πίνακα στο μηδέν, μεtable[move_x[rundo]][move_y[rundo]] = 0
- Κρύβουμε (κατά τυχαίο τρόπο ή βάσει κάποιου pattern) ορισμένα από τα ψηφία.
- Τυπώνουμε τον πίνακα
table
Πολύ μιλάς… Give me the code now!
[Το παρακάτω είναι η βασική ρουτίνα σε PHP - κάποιες συναρτήσεις που καλούνται από τη βασική, μπορούν εύκολα να προστεθούν. Για πλήρες reference, μπορείτε να ρίξετε μια ματιά στον ανοικτό κώδικα του XSudoku που είχα γράψει παλιότερα σε Pascal/Delphi]
function createSudoku() {
global $move_x;
global $move_y;
global $move_no;
global $table;
global $checked;
srand(time());
initTable();
$moves_made=0;
$max_moves_made=0;
while (!finished())
{
$xrand=(rand()%9)+1;
$yrand=(rand()%9)+1;
while ($table[$xrand][$yrand]!=0)
{
$xrand=(rand()%9)+1;
$yrand=(rand()%9)+1;
}
uncheck();
$Done=false;
while ((!$Done)&&(!allChecked()))
{
$num=(rand()%9)+1;
$checked[$num]=true;
if (OK($xrand,$yrand,$num))
{
$Done=true;
$table[$xrand][$yrand]=$num;
$moves_made++;
$move_x[$moves_made]=$xrand;
$move_y[$moves_made]=$yrand;
$move_no[$moves_made]=$num;
}
}
if ((allChecked())&&(!$Done))
{
$rundo=(rand()%(moves_made+1))+1;
$table[$move_x[$rundo]][$move_y[$rundo]]=0;
for ($zz=$rundo;$zz<=$moves_made-1;$zz++)
{
$move_x[$zz]=$move_x[$zz+1];
$move_y[$zz]=$move_y[$zz+1];
$move_no[$zz]=$move_no[$zz+1];
}
$moves_made--;
}
if ($moves_made>$max_moves_made)
$max_moves_made=$moves_made;
}
}
Κάνε ένα σχόλιο τώρα (όσο προλαβαίνεις...)