You are on page 1of 104

Pythonies

Προγραμματισμός
μέσα από παραδείγματα
στη γλώσσα Python

Βασίλης Βασιλάκης
Γιώργος Μπουκέας

pythonies.mysch.gr
twitter.com/pythonies
github.com/boukeas/pythonies
Οδηγίες Χρήσης 0
19 Οκτωβρίου 2015
09:10

Το αντικείμενο αυτού του βιβλίου είναι ο προγραμματισμός και η


ανάπτυξη ενός συγκεκριμένου τρόπου σκέψης που τον συνοδεύει.
Τα περισσότερα βιβλία αυτού του είδους είναι δομημένα γύρω από
προγραμματιστικές έννοιες, οι οποίες εισάγονται σταδιακά. Η κα-
τανόηση των εννοιών και η εμβάθυνση σε αυτές επιτυγχάνονται με
ασκήσεις, κατά κανόνα μικρές και εστιασμένες.
Εδώ δοκιμάζουμε μια διαφορετική προσέγγιση. Κάθε κεφάλαιο αυ-
τού του βιβλίου αντιστοιχεί σ’ ένα πρόγραμμα, συνήθως ένα παι-
χνίδι. Δεν το παρουσιάζουμε ολοκληρωμένο για να εξηγήσουμε στη
συνέχεια τον κώδικά του, αλλά ξεκινάμε από το μηδέν και το χτί-
ζουμε βήμα προς βήμα, εξηγώντας τη φιλοσοφία με την οποία περ-
νάμε από το κάθε βήμα στο επόμενο. Οι προγραμματιστικές έννοιες
δεν είναι το επίκεντρο· εισάγονται όταν προκύψει η ανάγκη και εξη-
γούνται στο βαθμό που εξυπηρετεί το συγκεκριμένο παράδειγμα.
Πιστεύουμε ότι η κατασκευή μεγαλύτερων προγραμμάτων παρέχει
στους αρχάριους βαθύτερο κίνητρο και αμεσότερη ανταμοιβή. Ελ-
πίζουμε ότι ο τρόπος ανάπτυξης του περιεχομένου επιτρέπει σ’ εκεί-
νους που το χρησιμοποιούν να γενικεύουν και να εφαρμόζουν τις
γνώσεις που αποκτούν και σε νέα προβλήματα.
Τα προγράμματα που κατασκευάζουμε δεν έχουν γραφικά ή ήχο,
ώστε να μπορέσουμε να εστιάσουμε στα θεμελιώδη. Κάποια από τα
κεφάλαια, όπως το Μάντεψε τον Αριθμό και η Κρεμάλα, αφορούν κλα-
σικά παραδείγματα που χρησιμοποιούνται παραδοσιακά σε εισα-
γωγικά μαθήματα επειδή είναι αρκετά απλά αλλά και ιδιαίτερα πε-
ριεκτικά. Aυτά έχουμε προσπαθήσει να τα εμπλουτίσουμε με εναλ-
λακτικές πινελιές. Ωστόσο, τα περισσότερα κεφάλαια ασχολούνται
με λιγότερο διαδεδομένα ή και εντελώς καινούργια παραδείγματα.
Θα γράφουμε τα προγράμματά μας στη γλώσσα Python. Θα μπο- Οι εξηγήσεις που σχετίζονται ειδικά
ρούσαμε να διαλέξουμε μια άλλη γλώσσα, όμως η Python είναι εξαι- με την Python έχουν διαχωριστεί από
το βασικό κείμενο και βρίσκονται εδώ,
ρετική για τον σκοπό μας. Προσπαθήσαμε το κείμενό μας να είναι στο περιθώριο.
ανεξάρτητο από τη γλώσσα προγραμματισμού. Οτιδήποτε έχει να
κάνει ειδικά με την Python θα βρίσκεται στο περιθώριο.

1
ΟΔΗΓΙΕΣ ΧΡΗΣΗΣ 2

Αν θέλετε να χρησιμοποιήσετε αυτό το βιβλίο για να μάθετε μό-


νοι σας προγραμματισμό, πρέπει να γνωρίζετε ότι θα χρειαστεί να
επενδύσετε αρκετό χρόνο και προσπάθεια. Όπως συμβαίνει και με
πολλά άλλα πράγματα, δεν γίνεται να μάθετε προγραμματισμό δια-
βάζοντας απλά ένα βιβλίο. Εξετάστε τον κώδικα που δίνεται, προ-
σπαθήστε να τον κατανοήσετε διαβάζοντας το συνοδευτικό κείμενο
και πληκτρολογήστε τον για να τον δείτε να εκτελείται. Αν το πρό-
γραμμα δεν εκτελείται ή αν δεν συμπεριφέρεται όπως περιμένετε
τότε είστε τυχεροί: έχετε κάνει κάποιο λάθος και η διαδικασία που
θα ακολουθήσετε για να το εντοπίσετε και να το διορθώσετε είναι
αυτή που θα σας προσφέρει μια βαθύτερη κατανόηση.
Καθώς πληκτρολογείτε τον κώδικα, δώστε προσοχή στους αριθμούς
των γραμμών που βρίσκονται αριστερά. Αυτοί θα σας βοηθήσουν
να καταλάβετε σε ποιο σημείο θα εισάγετε νέο κώδικα ή θα αντι-
καταστήσετε τμήματα του ήδη υπάρχοντος. Αν ο κώδικας δεν είναι
αριθμημένος, τότε πρόκειται απλά για μια εναλλακτική πρόταση.
Πιθανώς να θέλετε να χρησιμοποιήσετε αυτό το βιβλίο για να δι-
δάξετε προγραμματισμό. Κι εμείς έτσι το χρησιμοποιούμε. Ίσως η
προσέγγισή του ταιριάζει περισσότερο σε σας ή τους μαθητές σας.
Σε αυτή την περίπτωση, θεωρήστε ότι το κείμενο που συνοδεύει τον
κώδικα αποτελεί μια πρόταση ως προς τα σημεία στα οποία θα πρέ-
πει να εστιάσετε τη διδασκαλία σας.
Ξεκινώντας, είναι απαραίτητο να κατεβάσετε και να εγκαταστήσετε Μπορείτε να κατεβάσετε την Python
από εδώ: python.org/downloads
την Python. Σε περίπτωση που χρειάζεστε βοήθεια, ανατρέξτε στην
ιστοσελίδα μας για κάποιες βασικές οδηγίες, συμβουλευτείτε οποια- Η Python κυκλοφορεί σε δύο εκδόσεις
δήποτε από τις πηγές που προτείνουμε στη βιβλιογραφία, χρησιμο- που δεν είναι συμβατές μεταξύ τους.
Το βιβλίο ακολουθεί την Python 3.
ποιήστε τον οδηγό για αρχαρίους (στ’ Αγγλικά) ή κάντε μια αναζή-
τηση στον Παγκόσμιο Ιστό. Στη διεύθυνση pythonies.mysch.gr
θα βρείτε όλο το υλικό του βιβλίου.
Ο «επίσημος» εισαγωγικός οδηγός
στη διεύθυνση: wiki.python.org/
moin/BeginnersGuide
Η Απάντηση 1
Στο κεφάλαιο αυτό θα γνωρίσουμε τα βασικά είδη εντολών που θα επι- 18 Αυγούστου 2016
τρέπουν στα προγράμματά μας να αλληλεπιδρούν με το χρήστη, δηλαδή 11:43
να του εμφανίζουν μηνύματα στην οθόνη και να του ζητούν να εισάγει
τιμές από το πληκτρολόγιο. Θα κάνουμε επίσης μια πρώτη γνωριμία με
ορισμένες θεμελιώδεις έννοιες τις οποίες θα συναντήσουμε συχνά και στα
επόμενα κεφάλαια. Κυρίως όμως θα μάθουμε την Απάντηση για τη Ζωή,
το Σύμπαν και τα Πάντα!
Πληκτρολογήστε τις εντολές που ακολουθούν. Ίσως να μην είναι άμεσα
κατανοητό πώς λειτουργούν, αλλά αυτό είναι φυσιολογικό και η βαθύτερη
κατανόηση θα έρθει με την εξάσκηση και τον πειραματισμό. Σκοπός είναι
να εξοικειωθείτε με τη γλώσσα, τις λεπτομέρειες της σύνταξής της και τα
βασικά της χαρακτηριστικά. Πειραματιστείτε ελεύθερα και μην απογοη-
τεύεστε αν κάνετε λάθη: η προσπάθεια να τα εντοπίσετε και να τα εξαλεί-
ψετε είναι κι αυτή κομμάτι του προγραμματισμού.
Έννοιες: είσοδος, έξοδος, μεταβλητές, δομή επιλογής.

Ο Douglas Adams, στο βιβλίο The Hitchhiker’s Guide to the Galaxy,


γράφει για μια υπερευφυή φυλή η οποία, κουρασμένη από τις δια-
φωνίες σχετικά με το νόημα της ζωής, αποφάσισε να φτιάξει έναν
υπολογιστή που θα έδινε οριστικά την απάντηση που αναζητούσαν.
Ο υπολογιστής χρειάστηκε 7.5 εκατομμύρια χρόνια για να υπολογί-
σει και να ελέγξει την Απάντηση για τη Ζωή, το Σύμπαν και τα Πάντα.
Η Απάντηση ήταν… σαράντα δύο.
Ο υπολογιστής λέγονταν Deep Thought και μπορούμε να φτιάξουμε
κι εμείς ένα πρόγραμμα σαν το δικό του. Επειδή δεν διαθέτουμε ανά-
λογη υπολογιστική ισχύ κι επειδή γνωρίζουμε ήδη την Απάντηση, Ο,τιδήποτε ακολουθεί το σύμβολο #
είναι ένα σχόλιο. Δεν αφορά τον υπο-
θα κλέψουμε λίγο: το πρόγραμμά μας δεν θα υπολογίζει την Απά-
λογιστή και αγνοείται κατά την εκτέ-
ντηση, αλλά μόνο θα την ανακοινώνει στο χρήστη. λεση του προγράμματος. Απευθύνεται
σε εκείνους που διαβάζουν τον κώδικα
και (πρέπει να) χρησιμεύει στην καλύ-
Πες Τουλάχιστον Μια Καλημέρα τερη κατανόησή του.

1 # χαιρετισμός Για να εμφανίσουμε ένα μήνυμα στην


2 print("Καλημέρα.") οθόνη χρησιμοποιούμε την print(),
δίνοντας ανάμεσα στις παρενθέσεις
το μήνυμα που θα εμφανιστεί.

1
Η ΑΠΑΝΤΗΣΗ 2

Είναι εκπληκτικό ότι σχεδόν σε κάθε βιβλίο για τον προγραμματι-


σμό, αυτό είναι το πρώτο παράδειγμα που συναντάει κανείς: πως να
κάνει τον υπολογιστή του να πει μια καλημέρα.

Πες και την Απάντηση


Στη συνέχεια, ο Deep Thought μπορεί να εμφανίσει την Απάντηση.
# εμφάνιση της Απάντησης
print("Η Απάντηση είναι... 42")
Το ίδιο αποτέλεσμα μπορεί να επιτευχθεί και με διαφορετικό τρόπο.
Μπορούμε να εμφανίσουμε με την
3 # ορισμός και εμφάνιση της Απάντησης
print() πολλές τιμές, αρκεί να τις
4 answer = 42 χωρίσουμε με κόμμα. Ανάμεσά στις τι-
5 print("Η Απάντηση είναι...", answer) μές εμφανίζεται ένα κενό, όμως αυτή
answer/src/answer.1.py η προκαθορισμένη συμπεριφορά είναι
δυνατόν ν’ αλλάξει.
Ενώ ο χρήστης βλέπει το ίδιο μήνυμα στην οθόνη, οι δύο print()
δεν κάνουν το ίδιο πράγμα. Η πρώτη εμφανίζει πάντα την τιμή 42,
ενώ η δεύτερη εμφανίζει την τιμή answer, όποια κι αν είναι αυτή.
Η answer είναι μια μεταβλητή. Μπορούμε να πούμε ότι δίνουμε στην answer
τιμή 42 το όνομα answer. Μπορούμε επίσης να πούμε ότι δίνουμε
στο όνομα answer την τιμή 42. Και οι δύο περιγραφές είναι ορθές,
42
είναι απλά θέμα οπτικής γωνίας. Σημασία έχει ότι οι μεταβλητές
επιτρέπουν στα προγράμματά μας να διατηρούν, να “θυμούνται” τις Σχήμα 1.1: Η μεταβλητή answer έχει
τιμές που είναι σημαντικές. Όταν συσχετίζουμε μια τιμή μ’ ένα όνομα την τιμή 42. Εναλλακτικά, θα λέγαμε
(όπως κάνουμε εδώ με το όνομα answer και την τιμή 42) μπορούμε ν’ ότι στην τιμή 42 έχει δοθεί το όνομα
answer.
αναφερθούμε σε αυτή κι αργότερα, διαφορετικά δεν έχουμε τρόπο
ανάκτησής της.
Η εντολή answer = 42 δεν διατυπώνει κάτι που πρέπει να ισχύει για
πάντα, είναι απλά μια εντολή που θα αντιστοιχίσει το όνομα answer
με την τιμή 42 όταν έρθει η σειρά της να εκτελεστεί. Θα μπορούσαμε
με μια αντίστοιχη εντολή στη συνέχεια ν’ αλλάξουμε την τιμή της
μεταβλητής answer, δηλαδή να συσχετίσουμε το όνομα με μια νέα
τιμή (αν και αυτό δεν θα χρειαστεί για την answer, γιατί η Απάντηση
είναι μία).
Σημειώστε ότι ο χρήστης δε γνωρίζει τίποτα για το όνομα answer
ή την αντίστοιχη τιμή. Για την ακρίβεια δε γνωρίζει καν για την
ύπαρξη της μεταβλητής. Ο χρήστης γνωρίζει μόνο ό,τι του εμφανίζει
το πρόγραμμα με εντολές εξόδου.

Για Περίμενε Λιγάκι


Πώς γίνεται να προσθέσουμε λίγο σασπένς; Μπορούμε να εισάγουμε μια
καθυστέρηση λίγο πριν την ανακοίνωση της Απάντησης;
Επειδή οι βασικές εντολές της Python δεν προσφέρουν κάποιο μηχα-
νισμό καθυστέρησης, θα χρησιμοποιήσουμε μια βιβλιοθήκη, η οποία
παρέχει τη λειτουργικότητα που μας χρειάζεται. Οι βιβλιοθήκες πε-
Η ΑΠΑΝΤΗΣΗ 3

ριέχουν έτοιμο κώδικα και τις συναντάμε στις περισσότερες γλώσ-


σες προγραμματισμού: είναι συλλογές από μικρά προγράμματα που
μπορούμε να χρησιμοποιήσουμε στα προγράμματά μας.
1 import time Για να χρησιμοποιήσουμε μια βιβλιο-
θήκη θα πρέπει πρώτα να την ει-
2 # χαιρετισμός σάγουμε (import). Εδώ θα εισάγουμε
3 print("Καλημέρα.") τη βιβλιοθήκη time (που περιέχει
έτοιμα υποπρογράμματα για τη δια-
4 # καθυστέρηση χείριση του χρόνου) και θα χρησιμο-
5 time.sleep(3) ποιήσουμε την sleep(), που καθυστε-
6 # ορισμός και εμφάνιση της Απάντησης ρεί την εκτέλεση του προγράμματος
για όσα δευτερόλεπτα καθορίσουμε.
7 answer = 42
8 print("Η Απάντηση είναι...", answer)
answer/src/answer.2.py

Αν θέλουμε να υπάρχει πραγματικό σασπένς και να είμαστε πιστοί


στο βιβλίο του Douglas Adams, θα πρέπει να εισάγουμε μια καθυστέ-
ρηση της τάξης των 7.5 εκατομυρίων ετών (σε δευτερόλεπτα). Ίσως
δεν θα έπρεπε να δοκιμάσετε τον κώδικα που ακολουθεί, μπορεί να
βαρεθείτε λιγάκι μέχρι να εμφανιστεί η Απάντηση.
4 # καθυστέρηση Το σύμβολο * αντιστοιχεί στην πράξη
του πολλαπλασιασμού. Σε άλλες εκ-
5 wait = 7500000 * 365 * 24 * 60 * 60 φράσεις μπορείτε επίσης να χρησιμο-
6 time.sleep(wait) ποιήσετε τα κλασικά + και -, καθώς
επίσης και το / για τη διαίρεση, το //
7 # ορισμός και εμφάνιση της Απάντησης για το πηλίκο της ακέραιας διαίρεσης
8 answer = 42 και το % για το υπόλοιπο της ακέραιας
9 print("Η Απάντηση είναι...", answer) διαίρεσης.
answer/src/answer.3.py
Αν θέλετε να διακόψετε την εκτέλεση
Η τιμή της μεταβλητής wait αντιστοιχεί στα δευτερόλεπτα καθυ- ενός προγράμματος, χρησιμοποιήστε
τον συνδυασμό πλήκτρων Ctrl + C .
στέρησης. Σε αντίθεση με την answer, η τιμή της wait δεν καθορί-
ζεται απευθείας αλλά προκύπτει από τον υπολογισμό της τιμής του
wait
γινομένου 7500000 * 365 * 24 * 60 * 60.

Πιάσαμε την Κουβέντα 236520000000000

Μέχρι στιγμής ο Deep Thought είναι λίγο απρόσωπος: απλά ανακοινώνει Σχήμα 1.2: Η τιμή της μεταβλητής
wait προέκυψε από τον υπολογισμό
την Απάντηση. Ο χρήστης δεν θα αλληλεπιδρά με το πρόγραμμα;
της τιμής μιας αριθμητικής έκφρασης.
Η ροή της πληροφορίας ανάμεσα στο πρόγραμμα και τον χρήστη εί-
ναι μονόδρομη. Στις περισσότερες περιπτώσεις ένα πρόγραμμα δεν
περιορίζεται στην εμφάνιση μηνυμάτων, αλλά χρειάζεται και να κά-
νει ερωτήσεις στον χρήστη, να του ζητήσει τιμές.
Θα προγραμματίσουμε τον Deep Thought έτσι ώστε να ζητάει το
όνομα του χρήστη και να τον καλημερίζει κατάλληλα. Έτσι θα έχουν
ένα στοιχειώδη διάλογο, πριν από την ανακοίνωση της Απάντησης.
Για να ζητήσουμε μια τιμή από το
2 # είσοδος ονόματος
πληκτρολόγιο χρησιμοποιούμε την
3 print("Πώς σε λένε;") input(), η οποία επιστρέφει μια
4 name = input() αλφαριθμητική τιμή: το κείμενο που
5 # χαιρετισμός πληκτρολογήθηκε από τον χρήστη.
6 print("Καλημέρα", name) Εδώ, στην τιμή αυτή δίνεται το όνομα
name.
Η ΑΠΑΝΤΗΣΗ 4

answer/src/answer.4.py

Εναλλακτικά:
# είσοδος ονόματος Η εμφάνιση ενός μηνύματος πριν την
ανάγνωση τιμών είναι τόσο συνηθι-
name = input("Πώς σε λένε; ")
σμένη που η input() μπορεί να δεχθεί
# χαιρετισμός ανάμεσα στις παρενθέσεις το μήνυμα
print("Καλημέρα", name) που πρέπει να εμφανιστεί.

Η τιμή της μεταβλητής name είναι το κείμενο που πληκτρολογεί ο


χρήστης και φυσικά δεν είναι γνωστό εκ των προτέρων. O Deep
Thought χρησιμοποιεί την τιμή της μεταβλητής name για να καλη-
μερίσει τον χρήστη, όποια κι αν είναι αυτή η τιμή.

Έχεις Ώρα;
Αν ένας χρήστης εκτελέσει το βράδι το πρόγραμμα που έχουμε φτιάξει
για τον Deep Thought, τότε θα δυσκολευτεί να πιστέψει ότι πρόκειται για
έναν υπερυπολογιστή που γνωρίζει την Απάντηση για τη Ζωή, το Σύμπαν
και τα Πάντα, αφού θα τον χαιρετίσει βραδιάτικα με ένα “Καλημέρα”.
Tο πρόγραμμα πρέπει να γνωρίζει την ώρα της ημέρας. Ευτυχώς ο
υπολογιστής μας ξέρει τι ώρα είναι, οπότε απλά θα τον ρωτήσουμε.
Η localtime() της βιβλιοθήκης time
5 # ανάκτηση τοπικής ώρας συστήματος
επιστρέφει πληροφορίες για την ημε-
6 hour = time.localtime().tm_hour ρομηνία και την ώρα του συστήμα-
τος. Οι συντακτικές λεπτομέρειες δεν
Εδώ όμως το ουσιαστικό πρόβλημα είναι ότι οι εντολές που θα πρέ-
έχουν σημασία αυτή την στιγμή.
πει να εκτελέσει το πρόγραμμά μας δεν είναι πάντα οι ίδιες. Θα πρέ-
πει να προγραμματίσουμε τον Deep Thought έτσι ώστε να ελέγχει
την ώρα της ημέρας και να εμφανίζει διαφορετικό μήνυμα ανάλογα
με το αποτέλεσμα του ελέγχου.
Η if συνοδεύεται από μια συνθήκη,
7 # εμφάνιση χαιρετισμού ανάλογα με την ώρα
η οποία ελέγχεται κατά την εκτέλεση
8 if hour < 14: του προγράμματος και μπορεί να είναι
9 print("Καλημέρα", name) αληθής (True) ή ψευδής (False).
10 else: Οι εντολές που ακολουθούν την if εί-
11 print("Καλησπέρα", name) ναι στοιχισμένες δεξιότερα από αυτή.
answer/src/answer.5.py Η στοίχιση επιτυγχάνεται εισάγοντας
κενά πριν από τις εντολές, τα οποία
Ο κώδικας ελέγχει την συνθήκη hour < 14, δηλαδή αν η τρέχουσα υποδηλώνουν ότι αυτές οι εντολές θα
ώρα είναι πριν τις 2 το μεσημέρι. Αν η συνθήκη ισχύει τότε εμφανίζει εκτελεστούν μόνο αν η συνθήκη είναι
το μήνυμα "Καλημέρα", αλλιώς το μήνυμα "Καλησπέρα". Με άλλα αληθής. Παρομοίως, οι εντολές που
λόγια, το πρόγραμμα επιλέγει την συμπεριφορά του ανάλογα με τις ακολουθούν την else είναι στοιχι-
σμένες δεξιότερα και θα εκτελεστούν
συνθήκες που επικρατούν την ώρα της εκτέλεσής του. Αυτό το νέο μόνο εφόσον η συνθήκη είναι ψευδής.
προγραμματιστικό εργαλείο ονομάζεται δομή επιλογής.
Το σύμβολο < χρησιμοποιείται για την
Μια πρακτική συμβουλή: θα πρέπει πάντα να εκτελούμε τα προ- σύγκριση τιμών, όπως επίσης και τα
γράμματά μας, ελέγχοντας ότι η συμπεριφορά τους είναι ορθή. Εδώ <=, > και >=. Για να ελέγξουμε αν δύο
τιμές είναι ίσες χρησιμοποιούμε το ==
το πρόγραμμά μας περιέχει μια δομή επιλογής με δύο πιθανές περι-
και για να ελέγξουμε αν είναι διαφο-
πτώσεις και θα πρέπει να ελέγξουμε ότι λειτουργεί σωστά και στις ρετικές το !=.
δύο. Αν το εκτελέσουμε πρωί και μας χαιρετίσει μ’ ένα "Καλημέρα",
Μην παραλείπετε το σύμβολο : μετά
δεν είναι καλή ιδέα να περιμένουμε μέχρι το βράδι για να ελέγξουμε την συνθήκη της if και την else.
αν αλλάζει η συμπεριφορά του.
Η ΑΠΑΝΤΗΣΗ 5

Η λύση είναι να τροποποιήσουμε προσωρινά το πρόγραμμά μας και


να καθορίσουμε εμείς την ώρα, αντί να χρησιμοποιήσουμε την ώρα
του συστήματος. Καθορίζοντας ότι είναι 10 το πρωί, το πρόγραμμα
θα εμφανίσει το πρώτο μήνυμα.
# ΓΙΑ ΕΛΕΓΧΟ: ορισμός ώρας
hour = 10
Όταν αλλάξουμε την τιμή της μεταβλητής hour σε 20 τότε, εκτός
συγκλονιστικού απροόπτου, θα δούμε το δεύτερο μήνυμα.
# ΓΙΑ ΕΛΕΓΧΟ: ορισμός ώρας
hour = 20

Πλήρες Τελικό Πρόγραμμα


1 import time

2 # είσοδος ονόματος
3 print("Πώς σε λένε;")
4 name = input()
5 # ανάκτηση τοπικής ώρας συστήματος
6 hour = time.localtime().tm_hour
7 # εμφάνιση χαιρετισμού ανάλογα με την ώρα
8 if hour < 14:
9 print("Καλημέρα", name)
10 else:
11 print("Καλησπέρα", name)

12 # καθυστέρηση
13 wait = 3
14 time.sleep(wait)
15 # ορισμός και εμφάνιση της Απάντησης
16 answer = 42
17 print("Η Απάντηση είναι...", answer)
answer/src/answer.final.py

Και Τώρα Τι;


Δεν μάθατε και λίγα για αρχή. Ίσως μάλιστα να αισθάνεστε λίγο
όπως ο Παπαγάλος, του Ζαχαρία Παπαντωνίου.

Σαν έμαθε τη λέξη καλησπέρα


ο παπαγάλος, είπε ξαφνικά:
«Είμαι σοφός, γνωρίζω ελληνικά.
Τι κάθομαι εδώ πέρα;»

Όπου ελληνικά, αντικαταστήστε με Pythonιά. Όμως είναι ανάγκη να


εμπεδώσετε τις έννοιες που συναντήσατε χρησιμοποιώντας τις σε
νέα προβλήματα (δηλαδή λύνοντας τις ασκήσεις που ακολουθούν).
Διαφορετικά, θα αισθανθείτε σίγουρα όπως ο Παπαγάλος:
Η ΑΠΑΝΤΗΣΗ 6

«Κυρ παπαγάλε, θα ’χομε την τύχη


ν’ ακούσωμε τις λες και πάρα πέρα;»
Ο παπαγάλος βήχει, ξεροβήχει…
μα τι να πει; Ξανάπε: «καλησπέρα».

Τροποποιήσεις – Επεκτάσεις
Στο πρόγραμμα που κατασκευάσαμε χρησιμοποιήσαμε την input()
για να ζητήσουμε το όνομα του χρήστη. Η τιμή που επιστρέφει η
input() είναι αλφαριθμητική, έχει δηλαδή την μορφή κειμένου. Αυτό
δεν είναι πρόβλημα όταν ζητάμε το όνομα του χρήστη γιατί κι αυτό
έχει την μορφή κειμένου. Όταν όμως θέλουμε να ζητήσουμε από το
χρήστη έναν ακέραιο ή έναν πραγματικό αριθμό (κάτι που θα χρεια-
στεί στις ασκήσεις που ακολουθούν), τότε θα πρέπει να μετατρέ-
ψουμε την αλφαριθμητική τιμή που επιστρέφει η input() σε αριθ-
μητική, με τον τρόπο που φαίνεται παρακάτω:
# το κείμενο που πληκτρολογεί ο χρήστης
# μετατρέπεται σε ακέραιο αριθμό και
# αποθηκεύεται στην μεταβλητή number
number = int(input())

# το κείμενο που πληκτρολογεί ο χρήστης


# μετατρέπεται σε πραγματικό αριθμό και
# αποθηκεύεται στην μεταβλητή number
number = float(input())
Οι int() και float() χρησιμοποιούνται γενικότερα όταν χρειάζε-
ται να μετατρέψουμε μια τιμή σε ακέραια ή πραγματική. Για παρά-
δειγμα, η εκφραση int(3.14159) έχει την ακέραια τιμή 3.
Κάτι άλλο που θα φανεί χρήσιμο σε κάποιες από τις ασκήσεις είναι
ο υπολογισμός του πηλίκου και του υπολοίπου της ακέραιας διαίρε-
σης δύο αριθμών. Αυτό γίνεται όπως στο παράδειγμα:
# πηλίκο ακέραιας διαίρεσης του number με το 10
q = number // 10
# υπόλοιπο ακέραιας διαίρεσης του number με το 2
r = number % 2
Τέλος, αν μετά από μια print δεν θέλετε ν’ αλλάζει η γραμμή, ορίστε
κατάλληλα την παράμετρο end της print. Δοκιμάστε τo παρακάτω
τμήμα κώδικα με και χωρίς την end, για να καταλάβετε τη διαφορά
στον τρόπο λειτουργίας.
# μετά το μήνυμα τυπώνεται το περιεχόμενο της end
# (εδώ, ένα κενό ή τα αποσιωπητικά)
# και το επόμενο μήνυμα τυπώνεται στην ίδια γραμμή
print("Καλημέρα.", end = " ")
print("Η Απάντηση είναι", end = "... ")
print("42")
Η ΑΠΑΝΤΗΣΗ 7

1.1 Μπορείτε να επεκτείνετε το πρόγραμμα του Deep Thought έτσι ώστε να


ρωτά το χρήστη ποιο είναι το έτος γέννησής του και να υπολογίζει την
ηλικία του. Για να το καταφέρετε αυτό, θα πρέπει το πρόγραμμά σας να
γνωρίζει ποιο είναι το τρέχον έτος:
year = time.localtime().tm_year
Στη συνέχεια, μπορείτε απλά να εμφανίσετε στο χρήστη την ηλικία του
(αν και πιθανότατα θα τη γνωρίζει) ή να χρησιμοποιήσετε την if για να
εμφανίζετε διαφορετικό μήνυμα ανάλογα με την ηλικία. Θα μπορούσατε,
για παράδειγμα, να μην εμφανίζετε την Απάντηση αν η ηλικία του χρή-
στη ξεπερνά κάποιο όριο.
answer/exercises/answer-age.py

1.2 Να τροποποιήσετε το πρόγραμμα του Deep Thought έτσι ώστε να ρωτάει


τον χρήστη για την Απάντηση, αντί να την ανακοινώνει απευθείας.
Ποιά είναι η Απάντηση;
Το πρόγραμμα θα ελέγχει αν ο χρήστης πράγματι γνωρίζει την Απάντηση Με το == μπορείτε να ελέγξετε αν δύο
και θα του εμφανίζει ανάλογο μήνυμα. τιμές είναι ίσες.

Όταν ζητάτε από το χρήστη την Απάντηση, εκείνος δεν είναι υποχρεωμέ-
νος να πληκτρολογήσει έναν αριθμό. Έτσι, αν το πρόγραμμά σας χρησι-
μοποιεί την int για να μετατρέψει την απάντηση του χρήστη σε ακέραια,
έχετε υπόψη ότι αυτή η μετατροπή ίσως να μην είναι εφικτή και να οδηγή-
σει σε μήνυμα σφάλματος:
ValueError: invalid literal for int() with base 10
Μια απλή λύση είναι να θεωρήσετε ότι η Απάντηση είναι "42" αντί για
42, δηλαδή κείμενο και όχι ακέραιος. Έτσι, θα μπορέσετε να την συγκρί-
νετε με την απάντηση του χρήστη, χωρίς να χρειαστεί να μετατρέψετε την
απάντηση του χρήστη σε ακέραιο με την int.
answer/exercises/answer-question.py
Στη συνέχεια, να επεκτείνετε το πρόγραμμά σας έτσι ώστε να δίνεται
μια δεύτερη ευκαιρία στο χρήστη, αν αυτός δεν γνωρίζει την Απάντηση.
Πιθανώς, πριν ο χρήστης ερωτηθεί για δεύτερη φορά, το πρόγραμμα θα
μπορούσε να του εμφανίζει κάποια υπόδειξη, π.χ. ότι η Απάντηση είναι
το γινόμενο δύο διαδοχικών μονοψήφιων ακεραίων ή ότι είναι ο τέταρτος
κατά σειρά θετικός ακέραιος που οι “γείτονές” του είναι πρώτοι αριθμοί.
answer/exercises/answer-question-extended.py

Ασκήσεις
1.3 Στη Σελήνη το βάρος ενός αντικειμένου είναι το 16 του βάρους του στη
Γη. Στην Αφροδίτη το βάρος ενός αντικειμένου είναι 0,9 φορές το βάρος
του στη Γη. Στον Ήλιο το βάρος ενός αντικειμένου είναι 27,07 φορές το
βάρος του στη Γη, αλλά όταν βρίσκεται κανείς εκεί η αύξηση του βάρους
δεν είναι το βασικότερο πρόβλημα. Να γράψετε πρόγραμμα που θα ζητάει
από το χρήστη το βάρος του στη Γη και θα εμφανίζει το βάρος του στη
Σελήνη, την Αφροδίτη και τον Ήλιο.
answer/exercises/weight.py
Η ΑΠΑΝΤΗΣΗ 8

1.4 Υπάρχουν πολλά παιχνίδια στα οποία κάποιος σας ζητά να σκεφτείτε
έναν μυστικό αριθμό και, μετά από μερικές ερωτήσεις και υπολογισμούς,
«μαντεύει» τον αριθμό που είχατε σκεφτεί. Ένα πολύ παλιό παράδειγμα
βασίζεται σ’ ένα θεώρημα του κινέζου Sun Tzu, που έζησε κάπου ανάμεσα
στον 3ο και τον 5ο αιώνα. Ας υποθέσουμε ότι ο άγνωστος αριθμός είναι ο
x. Αν ονομάσουμε α, β και γ τα υπόλοιπα της διαίρεσης του x με το 3, το
5 και το 7 αντίστοιχα, τότε για να βρούμε τον άγνωστο αριθμό θα πρέπει
να υπολογίσουμε την τιμή της παράστασης 70α + 21β + 15γ και στη
συνέχεια να διαιρέσουμε με το 105. Το υπόλοιπο της διαίρεσης θα είναι
ο μυστικός αριθμός. Να αναπτύξετε ένα πρόγραμμα το οποίο ζητάει από
το χρήστη να σκεφτεί έναν μυστικό αριθμό από το 1 μέχρι και το 100.
Στη συνέχεια, το πρόγραμμα ρωτάει το χρήστη ποιο είναι το υπόλοιπο
της διαίρεσης αυτού του αριθμού με το 3, το 5 και το 7 και ανακοινώνει
τον μυστικό αριθμό, αφήνοντας το χρήστη έκπληκτο με τις υπερφυσικές
του ικανότητες.
answer/exercises/suntzu.py

1.5 Να γράψετε πρόγραμμα που θα ζητάει από το χρήστη το πλήθος των δευ-
τερολέπτων που έχουν περάσει από τα μεσάνυχτα και θα εμφανίζει την
τρέχουσα ώρα στη μορφή ώρες:λεπτά:δευτερόλεπτα. Για παράδειγμα,
αν ο χρήστης καθορίσει ότι έχουν περάσει 42222 δευτερόλεπτα από τα
μεσάνυχτα, το πρόγραμμα θα απαντά ότι η ώρα είναι 11:43:42.
Σημείωση: Αν οι ώρες, τα λεπτά ή τα δευτερόλεπτα είναι μονοψήφιοι αριθ-
μοί τότε η ώρα δεν θα εμφανίζεται «όμορφα». Αν αυτό προσβάλλει την
αισθητική σας και δεν μπορείτε να το ανεχτείτε, έχετε υπόψη ότι μπορείτε
να εμφανίσετε έναν αριθμό ως διψήφιο, με ένα αρχικό μηδέν όταν είναι
απαραίτητο, με τον εξής φρικτό τρόπο:
# έστω num ένας ακέραιος, πιθανώς μονοψήφιος
# έτσι εμφανίζεται με αρχικό μηδέν, αν χρειάζεται
print("{:02}".format(num))
answer/exercises/clock.py

1.6 Στο βιβλίο του Yacov Perelman με τίτλο Figures for Fun: Stories and
Conundrums υπάρχει το εξής πρόβλημα: Ένας άνθρωπος αδειάζει σ’ ένα
τραπέζι ένα κουτί που περιέχει 48 σπίρτα και τα χωρίζει σε τρεις άνισους
σωρούς. Παίρνει από τον πρώτο σωρό τόσα σπίρτα όσα υπάρχουν στο
δεύτερο σωρό και τα προσθέτει στον δεύτερο σωρό. Στη συνέχεια, παίρνει
από το δεύτερο σωρό τόσα σπίρτα όσα υπάρχουν στον τρίτο σωρό και
τα προσθέτει στον τρίτο σωρό. Τέλος, παίρνει από τον τρίτο σωρό τόσα
σπίρτα όσα υπάρχουν στον πρώτο σωρό και τα προσθέτει στον πρώτο
σωρό. Μετά από αυτές τις μετακινήσεις, όλοι οι σωροί περιέχουν το ίδιο
πλήθος σπίρτων. Πόσα σπίρτα περιείχε κάθε σωρός αρχικά;
Να γράψετε ένα πρόγραμμα που υπολογίζει τη λύση του προβλήματος,
ακολουθώντας τη διαδικασία που περιγράφεται αντίστροφα. Χρησιμο-
ποιήστε τρεις μεταβλητές h1, h2 και h3 για το πλήθος των σπίρτων
που περιέχει κάθε σωρός. Αρχικά, οι τρεις μεταβλητές θα έχουν την ίδια
τιμή, αντιστοιχώντας στην τελική κατάσταση. Ακολουθήστε με την αντί-
στροφη σειρά τα βήματα που περιγράφονται, γράφοντας τις αντίστοιχες
εντολές. Όταν τελειώσετε και εκτελέσετε το πρόγραμμα, οι μεταβλητές
Η ΑΠΑΝΤΗΣΗ 9

h1, h2 και h3 θα πρέπει να αντιστοιχούν στην αρχική κατάσταση των


σωρών, δηλαδή στην απάντηση που αναζητούμε.
Στη συνέχεια, συμπληρώστε το πρόγραμμα με τις κατάλληλες εντολές
έτσι ώστε να γίνεται και η επαλήθευση της απάντησης. Δηλαδή, με βάση
το αρχικό πλήθος σπίρτων που υπολογίσατε για τους τρεις σωρούς, ακο-
λουθήστε κανονικά τα βήματα όπως περιγράφονται, γράφοντας τις αντί-
στοιχες εντολές. Το πρόγραμμα θα πρέπει στο τέλος να ελέγχει αν οι με-
ταβλητές h1, h2 και h3 έχουν την ίδια τιμή, επαληθεύοντας τη λύση.
Δώστε ιδιαίτερη σημασία στη σειρά με την οποία πρέπει να εκτελεστούν οι
εντολές που μεταβάλλουν τις μεταβλητές h1, h2 και h3. Αν χρειάζεστε έναν
“σκελετό” για τη λύση της άσκησης, ανατρέξτε στο answer/exercises/
matches-incomplete.py
answer/exercises/matches.py

Εισοδος και Εξοδος Η ανάγνωση των τιμών που πληκτρολογεί ο χρήστης και γενικότερα η ροή πληροφορίας
προς ένα πρόγραμμα από το περιβάλλον του ονομάζεται είσοδος. Η εμφάνιση μηνυμάτων και γενικότερα η ροή
πληροφορίας από ένα πρόγραμμα προς το περιβάλλον του ονομάζεται έξοδος.

Μεταβλητες Μεταβλητή είναι το όνομα που δίνει ο προγραμματιστής σε μια τιμή ή ένα αντικείμενο. Χρειαζό-
μαστε τις μεταβλητές για τον ίδιο λόγο που χρειαζόμαστε γενικά τα ονόματα: για να μπορούμε να αναφερθούμε
σε αυτές τις τιμές, ακόμα και όταν δεν γνωρίζουμε ή δεν έχει σημασία ποιες ακριβώς είναι αυτές. Η τιμή στην
οποία αναφέρεται ένα όνομα, η τιμή της μεταβλητής, μπορεί να αλλάξει καθώς εκτελείται ένα πρόγραμμα. Είναι
καλή πρακτική να επιλέγουμε περιγραφικά ονόματα για τις μεταβλητές μας, ονόματα που υποδηλώνουν το είδος
των τιμών στις οποίες αντιστοιχούν.

Δομη Επιλογης Η δομή επιλογής δίνει τη δυνατότητα στα προγράμματά μας να ελέγχουν συνθήκες και να
διαφοροποιούν την συμπεριφορά τους ανάλογα με αυτές. Χωρίς αυτή τη δυνατότητα, τα προγράμματά μας εκτε-
λούν πάντα τις ίδιες εντολές, με την ίδια σειρά και αυτό τους στερεί την προσαρμοστικότητα ή την “εξυπνάδα”
που είναι απαραίτητη ακόμα και σε πολύ απλά προβλήματα.

Σκουπιδια Μπαινουν, Σκουπιδια Βγαινουν Στο βιβλίο, ο Deep Thought προσφέρει μια εξήγηση για το παρά-
δοξο της Απάντησης: “Για να είμαι ειλικρινής, πιστεύω ότι το πρόβλημα είναι ότι ποτέ δεν γνωρίζατε πραγματικά
ποια είναι η ερώτηση. Αν μάθετε την ερώτηση, τότε θα καταλάβετε τι σημαίνει και η απάντηση.” Τα προγράμματα
τα γράφουν προγραμματιστές. Άνθρωποι. Οι υπολογιστές απλά τα εκτελούν κι ελπίζουμε ότι η απάντηση που
μας δίνουν αποτελεί τη λύση στο πρόβλημά μας. Για να γίνει όμως αυτό, θα πρέπει οι προγραμματιστές να έχουν
κατανοήσει σωστά το πρόβλημα, οι οδηγίες που έχουν καθορίσει να είναι ορθές και τα δεδομένα που παρέχουν
οι χρήστες να έχουν νόημα. Σε διαφορετική περίπτωση, η απάντηση θα μας είναι άχρηστη. Ο Charles Babbage,
ο άνθρωπος που σχεδίασε έναν μηχανικό προγραμματιζόμενο υπολογιστή εκατό χρόνια πριν κατασκευαστούν
οι πρώτοι ηλεκτρονικοί υπολογιστές, είχε γράψει: “Σε δύο περιπτώσεις έχω ερωτηθεί: Πείτε μας κύριε Babbage,
αν εισάγετε λάθος νούμερα στη μηχανή, θα βγουν οι σωστές απαντήσεις; … Δεν μπορώ να συλλάβω τι είδους
σύγχυση ιδεών θα προκαλούσε μια τέτοια ερώτηση.”

Τα Τεσσερα Καπελα Συνήθως όλα ξεκινούν από ένα πρόβλημα. Κάποιος σκέφτεται “δεν θα ήταν ωραία αν
είχαμε ένα πρόγραμμα που να μας λύνει αυτό το πρόβλημα;” Μέσα σ’ αυτό το βιβλίο υπάρχουν αρκετά έτοιμα
προβλήματα, αλλά σύντομα θα θελήσετε κι εσείς να φορέσετε το καπέλο του Προβληματοθέτη, γιατί είναι
αλήθεια ότι τα πιο ενδιαφέροντα προβλήματα είναι αυτά που έχουν σημασία για εσάς. Μετά, υπάρχει κάποιος
Η ΑΠΑΝΤΗΣΗ 10

που φοράει το καπέλο του Προγραμματιστή. Διατυπώνει, σε μια γλώσσα προγραμματισμού, τις εντολές που
πρέπει να εκτελεστούν για να λυθεί το πρόβλημα. Εσείς πιθανότατα διαβάζετε αυτό το βιβλίο ακριβώς για να
μάθετε πως μπορείτε να παίξετε αυτό το ρόλο. Tα προγράμματα τα εκτελούν οι υπολογιστές, οι οποίοι είναι
φτιαγμένοι ειδικά γι’ αυτόν το σκοπό, όμως πολύ συχνά θα φορέσετε κι εσείς το καπέλο του Εκτελεστή και θα
εκτελέσετε εσείς οι ίδιοι τα προγράμματά σας, για να καταλάβετε πως λειτουργούν οι εντολές που γράψατε και,
πιθανώς, γιατί δεν λειτουργούν όπως θα θέλατε. Τέλος, υπάρχουν εκείνοι που χρησιμοποιούν το πρόγραμμα,
αλληλεπιδρούν μαζί του και αυτό λύνει για λογαριασμό τους το πρόβλημα από το οποίο ξεκίνησαν όλα. Θα
φοράτε κι εσείς το καπέλο του Χρήστη όταν ελέγχετε τα προγράμματά σας ή απλά διασκεδάζετε με αυτά.
Μπαρμπούτι 2
Σε αυτό το κεφάλαιο θα φτιάξουμε ένα παιχνίδι με ζάρια. Στην πορεία θα 29 Αυγούστου 2016
εξασκηθούμε στη δομή επιλογής και θα γνωρίσουμε τη δομή επανάληψης, 11:35
που μας επιτρέπει να εκτελούμε τις ίδιες εντολές πολλές φορές. Το τελικό
πρόγραμμα θα είναι μικρό, όμως σύντομα θα χρειαστεί να γράψουμε προ-
γράμματα μεγαλύτερα και πιο περίπλοκα. Έτσι, το ουσιαστικό αντικείμενο
του κεφαλαίου είναι να έρθουμε σε επαφή με τον τρόπο που οι προγραμ-
ματιστές συνθέτουν τα προγράμματά τους από υποπρογράμματα, σα να συ-
ναρμολογούν ένα περίπλοκο μηχάνημα από απλούστερα εξαρτήματα.
Έννοιες: δομή επιλογής, δομή επανάληψης, υποπρογράμματα

Σε πολλές αμερικάνικες ταινίες οι πρωταγωνιστές κάνουν μια βόλτα


από το Λας Βέγκας και γίνονται εκατομμυριούχοι ή χάνουν τα πά-
ντα παίζοντας ένα παιχνίδι με ζάρια, άγνωστο στους περισσότε-
ρους. Η κοντινότερη ελληνική εκδοχή αυτού του παιχνιδιού είναι
το μπαρμπούτι, πηγή έμπνευσης για πολλούς άτυχους ρεμπέτες.
Οι κανόνες είναι οι εξής: Αρχικά, ο παίκτης ρίχνει δύο ζάρια. Αν
το άθροισμα των ενδείξεών τους είναι 7 ή 11, τότε ο παίκτης κερδί-
ζει, ενώ αν είναι 2, 3 ή 12 χάνει. Σε οποιαδήποτε άλλη περίπτωση,
το άθροισμα των ενδείξεων γίνεται ο “στόχος” και ο παίκτης ρίχνει
επαναλαμβανόμενα τα ζάρια μέχρι να ρίξει την ίδια ζαριά (να πετύχει
τον στόχο), οπότε και κερδίζει, ή μέχρι να φέρει 7, οπότε και χάνει.

Τα Κόκκαλα Στον Μάστορα


Πώς θα προσομοιώσουμε το ρίξιμο των ζαριών;
Θα ασχοληθούμε αρχικά με την πρώτη ζαριά του παίκτη, με την
οποία ξεκινά το παιχνίδι. Από την κατάλληλη βιβλιοθήκη θα χρη- Για να χρησιμοποιήσουμε μια βιβλιο-
σιμοποιήσουμε έναν μηχανισμό παραγωγής τυχαίων αριθμών. θήκη θα πρέπει πρώτα να την εισά-
γουμε (import). Εδώ θα εισάγουμε τη
1 import random βιβλιοθήκη random και θα χρησιμο-
2 # τυχαίες τιμές για τα δύο ζάρια ποιήσουμε την randint(), που παρά-
γει τυχαίους ακέραιους εντός συγκε-
3 dice1 = random.randint(1,6)
κριμένων ορίων που καθορίζει ο προ-
4 dice2 = random.randint(1,6) γραμματιστής.

1
ΜΠΑΡΜΠΟΥΤΙ 2

Οι μεταβλητές dice1 και dice2 αντιστοιχούν στις ενδείξεις των δύο


ζαριών και παίρνουν μια τυχαία τιμή από το 1 μέχρι και το 6. Κάθε
φορά που θα εκτελείται το πρόγραμμα η τιμή αυτή θα είναι (πιθα-
νώς) διαφορετική.
Μένει τώρα να εμφανίσουμε τη ζαριά στο χρήστη, αφού πρώτα υπο-
λογίσουμε το άθροισμα των δύο ενδείξεων dice1 και dice2.
5 # υπολογισμός και εμφάνιση ζαριάς
6 roll = dice1 + dice2
7 print("Έριξες", dice1, dice2, "=", roll)
craps/src/craps.1.py

Η εντολή roll = dice1 + dice2 σημαίνει: υπολόγισε το άθροισμα


των τιμών dice1 και dice2 κι ονόμασε το αποτέλεσμα roll. Αυτό θα
συμβεί μια φορά, όταν εκτελεστεί η εντολή, και θα χρησιμοποιηθούν
οι τιμές που έχουν οι μεταβλητές dice1 και dice2 εκείνη την στιγμή.
Αν οι dice1 και dice2 αργότερα αλλάξουν τιμή, δεν θα κάνει το
ίδιο αυτόματα και η roll. Η roll θα αλλάξει τιμή μόνο όταν της
αποδοθεί ρητά, με ένα =, μια (οποιαδήποτε) άλλη τιμή.
Όπως έχει τώρα το πρόγραμμα, τα ζάρια ρίχνονται αμέσως μόλις
ξεκινήσει το παιχνίδι. Είναι όμως καλύτερο ν’ αποφασίζει ο παίκτης
πότε θα ριχτεί η ζαριά, για να του δώσουμε την αίσθηση ότι παίζει.
Εδώ η τιμή που επιστρέφει η input(),
2 # προτροπή για ρίψη ζαριών
δηλαδή το κείμενο που πληκτρολογεί
3 print("Ρίξε τα ζάρια με ENTER...", end="") ο χρήστης, δεν αποδίδεται σε κάποια
4 input() μεταβλητή και χάνεται, αφού δεν μας
5 # τυχαίες τιμές για τα δύο ζάρια ενδιαφέρει να τη διατηρήσουμε.
6 dice1 = random.randint(1,6) Για να μην αλλάξει η γραμμή μετά
7 dice2 = random.randint(1,6) την εμφάνιση της προτροπής, ορί-
ζουμε την παράμετρο end της print()
Τώρα τα ζάρια ρίχνονται αφού πρώτα ο παίκτης προτρέπεται να να είναι ίση με το κενό κείμενο.
πατήσει το πλήκτρο ENTER.

Πάλι Ντόρτια Ήφερα


Και πώς θα ξέρουμε αν ο παίκτης κέρδισε, έχασε ή πρέπει να ξαναρίξει;
Στο σημείο αυτό, υπάρχουν τρεις διαφορετικές περιπτώσεις, τρεις
πιθανές εκδοχές για την συνέχεια του παιχνιδιού: νίκη (με 7 ή 11),
ήττα (με 2, 3 ή 12) ή επανάληψη (σε οποιαδήποτε άλλη περίπτωση).
Σε κάθε περίπτωση είναι διαφορετικές οι εντολές που θα πρέπει να
εκτελεστούν.
Κατά τη διάρκεια συγγραφής του προγράμματος, δεν είναι δυνατό
να γνωρίζουμε εκ των προτέρων ποια θα είναι κάθε φορά η ζαριά,
οπότε θα χρησιμοποιήσουμε μια δομή επιλογής, με την οποία θα κα-
θορίσουμε ποιες εντολές θα πρέπει να εκτελεστούν σε κάθε μία από
τις τρεις πιθανές περιπτώσεις.
ΜΠΑΡΜΠΟΥΤΙ 3

11 # έλεγχος αποτελέσματος Κάθε if συνοδεύεται από μια συνθήκη,


η οποία ελέγχεται κατά την εκτέλεση
12 if roll == 7 or roll == 11: του προγράμματος και μπορεί να είναι
13 # νίκη με την πρώτη αληθής (True) ή ψευδής (False).
14 print("Κέρδισες με την πρώτη!")
Το elif σημαίνει else if. Μετά από
15 elif roll <= 3 or roll == 12: μια if μπορούμε να χρησιμοποιή-
16 # ήττα με την πρώτη σουμε όσες elif είναι απαραίτητες.
17 print("Έχασες με την πρώτη...") Οι εντολές που ακολουθούν τις if
18 else: και elif είναι στοιχισμένες δεξιότερα.
19 # τίθεται ο "στόχος" Η στοίχιση υποδηλώνει ότι αυτές οι
20 print("Ξαναρίξε. Πρέπει να φέρεις", roll) εντολές θα εκτελεστούν μόνο αν η
αντίστοιχη συνθήκη είναι αληθής και
Κατά την εκτέλεση του προγράμματος ελέγχονται διαδοχικά οι συν- καμία από τις προηγούμενες συνθήκες
θήκες, η μία μετά την άλλη, μέχρι να βρεθεί μία που να είναι αλη- δεν είναι αληθείς. Οι εντολές που ακο-
λουθούν την else είναι επίσης στοιχι-
θής. Όταν συμβεί αυτό, εκτελούνται οι αντίστοιχες εντολές και δεν
σμένες δεξιότερα και θα εκτελεστούν
ελέγχεται άλλη συνθήκη. Αν διαπιστωθεί ότι όλες οι συνθήκες είναι μόνο εφόσον οι προηγούμενες συνθή-
ψευδείς, εκτελούνται οι εντολές της else, αν υπάρχουν. Επομένως, κες είναι ψευδείς.
τελικά θα εκτελεστούν μόνο είτε οι εντολές που αντιστοιχούν στην Μην παραλείπετε το σύμβολο : μετά
πρώτη αληθή συνθήκη, είτε οι εντολές της else, είτε καμία εντολή. τις συνθήκες και την else.
Με το == ελέγχεται αν δύο τιμές είναι
Ξαναρίχνοντας τα Ζάρια ίσες. Διαφέρει από το = που χρησιμο-
ποιείται για να δώσουμε τιμή σε μια
Η τρίτη περίπτωση δεν είναι ολοκληρωμένη. Αν ο παίκτης δεν κερδίσει, μεταβλητή.
ούτε χάσει με την πρώτη, πρέπει να ξαναρίξει τα ζάρια. Το or χρησιμοποιείται για τη διά-
Το πρόγραμμα περιέχει ήδη ένα σύνολο εντολών που “ρίχνουν” τα ζευξη δύο συνθηκών. Η διάζευξη είναι
αληθής όταν τουλάχιστον μια από τις
δύο ζάρια. Τις έχουμε χρησιμοποιήσει για την πρώτη ζαριά και μπο- διαζευγμένες συνθήκες είναι αληθής.
ρούμε να τις αντιγράψουμε και στην περίπτωση όπου ο παίκτης πρέ- Υπάρχει και o τελεστής and. Χρησιμο-
πει να ξαναρίξει τα ζάρια. ποιείται για τη σύζευξη δύο συνθηκών,
η οποία είναι αληθής όταν και οι δύο
else: συζευγμένες συνθήκες είναι αληθείς.
# τίθεται ο "στόχος"
print("Ξαναρίξε. Πρέπει να φέρεις", roll)
# προτροπή για ρίψη ζαριών
print("Ρίξε τα ζάρια με ENTER...", end="")
input()
αποτέλεσμα
# τυχαίες τιμές για τα δύο ζάρια roll πρώτης ζαριάς
dice1 = random.randint(1,6)
dice2 = random.randint(1,6) Σχήμα 2.1: Αρχικά, η τιμή της μετα-
# υπολογισμός και εμφάνιση ζαριάς βλητής roll αντιστοιχεί στο αποτέ-
λεσμα της πρώτης ζαριάς.
roll = dice1 + dice2
print("Έριξες", dice1, dice2, "=", roll) αποτέλεσμα
πρώτης ζαριάς
Κάποια στιγμή θα πρέπει να ελεγχθεί αν η δεύτερη ζαριά του παίκτη
αποτέλεσμα
είναι ίση με την πρώτη, για να διαπιστωθεί αν κέρδισε ή όχι. Το roll δεύτερης ζαριάς
πρόβλημα είναι ότι, με το πρόγραμμα ως έχει, αυτό είναι αδύνατο:
Όταν ρίχνεται η δεύτερη ζαριά, το αποτέλεσμά της ονομάζεται και Σχήμα 2.2: Στη συνέχεια, η roll χρη-
σιμοποιείται για το αποτέλεσμα της
πάλι roll. Η προηγούμενη τιμή της roll, η τιμή της πρώτης ζαριάς,
δεύτερης ζαριάς και η τιμή της τροπο-
δεν είναι πια διαθέσιμη και δεν μπορεί να γίνει σύγκριση μεταξύ των ποιείται. Το αποτέλεσμα της πρώτης
δύο τιμών (δείτε σχετικά και τα σχήματα 2.1 και 2.2). ζαριάς δεν είναι πια διαθέσιμο.
ΜΠΑΡΜΠΟΥΤΙ 4

Για να λυθεί το πρόβλημα θα χρησιμοποιήσουμε μια διαφορετική με- αποτέλεσμα


ταβλητή, ένα διαφορετικό όνομα για τη δεύτερη ζαριά. roll πρώτης ζαριάς
αποτέλεσμα
18 else: newroll δεύτερης ζαριάς
19 # τίθεται ο "στόχος"
20 print("Ξαναρίξε. Πρέπει να φέρεις", roll) Σχήμα 2.3: Το αποτέλεσμα της δεύ-
21 # προτροπή για ρίψη ζαριών τερης ζαριάς ονομάζεται newroll. Οι
δυο ζαριές, οι τιμές roll και newroll
22 print("Ρίξε τα ζάρια με ENTER...", end="") μπορούν να συγκριθούν μεταξύ τους.
23 input()
24 # τυχαίες τιμές για τα δύο ζάρια
25 dice1 = random.randint(1,6)
26 dice2 = random.randint(1,6)
27 # υπολογισμός και εμφάνιση ζαριάς
28 newroll = dice1 + dice2
29 print("Έριξες", dice1, dice2, "=", newroll)
Τώρα μπορούμε να συγκρίνουμε τη δεύτερη ζαριά με την πρώτη, δη-
λαδή την τιμή της newroll με εκείνη της roll για να εξετάσουμε αν
ο παίκτης κέρδισε ή όχι.
28 newroll = dice1 + dice2
29 print("Έριξες", dice1, dice2, "=", newroll)
30 # έλεγχος αποτελέσματος
31 if newroll == roll:
32 print("Κέρδισες!")
33 elif newroll == 7:
34 print("Έχασες...")
craps/src/craps.2.py

Ξανά Και Ξανά


Η τρίτη περίπτωση ακόμα δεν είναι ολοκληρωμένη. Αν ο παίκτης δεν Στην Python, η βασική επαναληπτική
κερδίσει, ούτε χάσει με την πρώτη, ίσως χρειαστεί να ρίξει περισσότερες δομή είναι η while, η οποία μπορεί να
χρησιμοποιηθεί σε κάθε περίπτωση,
από μία επιπλέον ζαριές. Πώς θα κάνουμε τις υπάρχουσες εντολές για τη
ενώ για συγκεκριμένου είδους επανα-
δεύτερη ζαριά να επαναλαμβάνονται; λήψεις υπάρχει και η δομή for.
Κάθε γλώσσα προγραμματισμού προσφέρει επαναληπτικές δομές, Η επαναληπτική δομή while συνο-
δηλαδή τρόπους να εκφράσει κανείς ότι ένα σύνολο εντολών θα δεύεται από μια συνθήκη συνέχειας. Η
πρέπει να επαναλαμβάνεται. συνθήκη ελέγχεται στην αρχή κάθε
νέου κύκλου της επανάληψης και μπο-
Μια τέτοια επαναληπτική δομή θα χρειαστεί να προσθέσουμε στην ρεί να είναι αληθής (True) ή ψευδής
τρίτη περίπτωση, όπου ο παίκτης ούτε κερδίζει, ούτε χάνει με την (False). Όσο η συνθήκη είναι αληθής,
η επανάληψη συνεχίζεται για άλλον
πρώτη. Μέσα στην επανάληψη θα τοποθετήσουμε τις εντολές που έναν κύκλο.
υλοποιούν την ρίψη των ζαριών και ελέγχουν το αποτέλεσμα. Οι
εντολές γράφονται μόνο μια φορά αλλά εκτελούνται ξανά και ξανά.
18 else: ✓
while
19 # τίθεται ο "στόχος"
20 print("Ξαναρίξε. Πρέπει να φέρεις", roll)
21 # επανάληψη ρίψεων
22 while True: Σχήμα 2.4: Η τετριμμένη συνθήκη
True που ελέγχεται εδώ από τη while
είναι πάντα αληθής, κι έτσι η συγκε-
κριμένη επανάληψη θα ξεκινήσει σί-
γουρα και δεν πρόκειται να διακοπεί
λόγω της συνθήκης.
ΜΠΑΡΜΠΟΥΤΙ 5

23 # προτροπή για ρίψη ζαριών Οι εντολές που ακολουθούν τη while


είναι στοιχισμένες δεξιότερα. Η στοί-
24 print("Ρίξε τα ζάρια με ENTER...", end="")
χιση αυτή επιτυγχάνεται εισάγοντας
25 input() κενά πριν από τις εντολές, τα οποία
26 # τυχαίες τιμές για τα δύο ζάρια υποδηλώνουν ότι αυτές οι εντολές
27 dice1 = random.randint(1,6) “ανήκουν” στη while και θα επανα-
28 dice2 = random.randint(1,6) λαμβάνονται. Η πρώτη εντολή μετά
τη while που δεν θα είναι στοιχισμένη
29 # υπολογισμός και εμφάνιση ζαριάς δεξιότερα δεν θα επαναλαμβάνεται,
30 newroll = dice1 + dice2 αλλά θα εκτελεστεί μόνο μια φορά,
31 print("Έριξες", dice1, dice2, "=", newroll) όταν η επανάληψη τερματιστεί.

Είναι Σημαντικό Να Έχεις Στόχους


Οι επαναλαμβανόμενες ρίψεις των ζαριών πρέπει κάποτε να σταματούν.
Πώς τερματίζω την επαναληπτική διαδικασία;
Ήδη το πρόγραμμά μας ελέγχει μετά από κάθε ζαριά την έκβασή της.
Υπάρχουν κι εδώ τρεις πιθανές περιπτώσεις: νίκη (ο παίκτης φέρνει
ζαριά ίση με τον στόχο), ήττα (ο παίκτης φέρνει 7) και συνέχεια της
while
επανάληψης (σε οποιαδήποτε άλλη περίπτωση). Στις δύο πρώτες
περιπτώσεις η επανάληψη θα πρέπει να τερματιστεί. Ένας απλός
τρόπος για να επιτευχθεί αυτό είναι με την προσθήκη της εντολής break
break, η οποία διακόπτει άμεσα τον κύκλο της επανάληψης.
32 # έλεγχος αποτελέσματος Σχήμα 2.5: Η break διακόπτει και τερ-
33 if newroll == roll: ματίζει άμεσα τον κύκλο της επανά-
34 print("Κέρδισες!") ληψης, χωρίς να ελεγχθεί η συνθήκη
συνέχειας. Οι εντολές μετά την break
35 # το παιχνίδι τελείωσε αγνοούνται και η εκτέλεση συνεχίζε-
36 break ται από την πρώτη εντολή που ακο-
37 elif newroll == 7: λουθεί την επανάληψη.
38 print("Έχασες...") Για να κατανοήσετε καλύτερα τι ση-
39 # το παιχνίδι τελείωσε μαίνει η άμεση διακοπή της επανάλη-
40 break ψης όταν εκτελεστεί η break, δοκιμά-
craps/src/craps.3.py στε να τοποθετήσετε τις print μετά
τις break. Θα διαπιστώσετε ότι τα μη-
Η χρήση της break είναι μια πρακτική που δεν ακολουθείται από νύματα δεν θα εμφανιστούν ποτέ.
όλους. Ορισμένοι θεωρούν ότι ο κώδικας είναι πιο κατανοητός όταν Η else δεν είναι υποχρεωτική σε μια
υπάρχει ένα μοναδικό σημείο εξόδου από την επανάληψη: η συν- δομή επιλογής και παραλείπεται όταν
θήκη συνέχειας. Θα εξετάσουμε μια εκδοχή στην οποία ο κύκλος της δεν υπάρχουν εντολές να εκτελεστούν
επανάληψης δεν διακόπτεται άμεσα με την break αλλά μόνο όταν στην τελευταία περίπτωση.
η συνθήκη συνέχειας ελεγχθεί και διαπιστωθεί ότι είναι ψευδής.
Θα χρησιμοποιήσουμε μια λογική μεταβλητή over για να “θυμάται” Υπάρχουν μόνο δύο λογικές τιμές:
το πρόγραμμά μας αν το παιχνίδι έχει τελειώσει. Αρχικά, πριν την True ή False.
επανάληψη, η over ορίζεται ως ψευδής (False).
18 else:
19 # τίθεται ο "στόχος"
20 print("Ξαναρίξε. Πρέπει να φέρεις", roll)
21 # το παιχνίδι δεν έχει τελειώσει
22 over = False
ΜΠΑΡΜΠΟΥΤΙ 6

Στην αρχή κάθε κύκλου της επανάληψης, ελέγχεται η συνθήκη συ-


νέχειας not over κι ένας νέος κύκλος ξεκινά μόνο εφόσον η over Το not χρησιμοποιείται πριν από μια
εξακολουθεί να είναι ψευδής. συνθήκη και αντιστρέφει την τιμή της:
όταν μια συνθήκη είναι ψευδής τότε η
21 # το παιχνίδι δεν έχει τελειώσει αντίστροφή της είναι αληθής και το
22 over = False ανάποδο.
23 # επανάληψη ρίψεων
24 while not over:
Η συνθήκη συνέχειας της επανάληψης θα μπορούσε εναλλακτικά
να γραφτεί και με τους δύο ακόλουθους τρόπους:
# επανάληψη ρίψεων
while over == False
# επανάληψη ρίψεων Με το != ελέγχεται αν δύο τιμές είναι
διαφορετικές.
while over != True
Η τιμή της over θα πρέπει να αλλάζει σε αληθής (True) όταν ο παί-
κτης κερδίσει ή χάσει – εκεί δηλαδή όπου στην προηγούμενη εκδοχή
συναντούσαμε την break.
34 # έλεγχος αποτελέσματος
35 if newroll == roll: ✗
36 print("Κέρδισες!") while
37 # το παιχνίδι τελείωσε
38 over = True
39 elif newroll == 7: over = True
40 print("Έχασες...")
41 # το παιχνίδι τελείωσε Σχήμα 2.6: Όταν η over πάρει τιμή
42 over = True True, η επανάληψη δεν θα διακοπεί
craps/src/craps.4.py άμεσα, γιατί η συνθήκη not over της
while δεν ελέγχεται συνεχώς. Η επα-
Στην εκδοχή αυτή η επανάληψη δεν θα σταματήσει άμεσα όταν η νάληψη θα διακοπεί όταν ελεγχθεί η
over γίνει αληθής, αλλά όταν ελεγχθεί η συνθήκη συνέχειας και συνθήκη της while, δηλαδή αφού τε-
διαπιστωθεί ότι η over είναι αληθής. λειώσει ο κύκλος της επανάληψης.

Μεταβλητές όπως η over που παίρνουν μόνο δύο τιμές και χρησι-
μοποιούνται ως ένδειξη ότι κάποιο γεγονός έχει συμβεί ή όχι ονομά-
ζονται συχνά σημαίες (flags).

Κάν’το Μου Λιανά


Το πρόγραμμα λειτουργεί, αλλά δεν μου αρέσει ο τρόπος που είναι γραμ- Tα υποπρογράμματα στην Python και
μένο. Οι εντολές για την ρίψη μιας ζαριάς επαναλαμβάνονται αυτούσιες σε πολλές άλλες γλώσσες έχουν τη
μορφή συναρτήσεων. Έχουν ένα όνομα
σε δύο σημεία του προγράμματος.
και μια (προαιρετική) σειρά παραμέ-
Οι ίδιες εντολές που προσομοιώνουν τη ρίψη των ζαριών χρησιμο- τρων, εντός παρενθέσεων. Όταν κλη-
θούν, οι συναρτήσεις δέχονται τιμές
ποιούνται τόσο στην αρχική ζαριά όσο και στις επαναλαμβανόμενες
για τις παραμέτρους, τις επεξεργάζο-
ζαριές. Αυτές οι εντολές δεν είναι ούτε αναγκαίο, ούτε σκόπιμο να νται και επιστρέφουν ένα αποτέλεσμα.
ξαναγράφονται. Ουσιαστικά, αποτελούν ένα ενιαίο σύνολο και υλο-
Έχουμε ήδη χρησιμοποιήσει συναρτή-
ποιούν μια συγκεκριμένη λειτουργία. Μπορούμε λοιπόν να τις τοπο- σεις που παρέχει έτοιμες η Python,
θετήσουμε μέσα σ’ ένα υποπρόγραμμα, δημιουργώντας ένα κλειστό όπως η print() και η input(), αλλά
“εξάρτημα” που υλοποιεί τη λειτουργία της ρίψης των ζαριών. και συναρτήσεις που ανήκουν σε βι-
βλιοθήκες, όπως time.sleep() και η
random.randint(). Τα προγράμματά
μας καλούν αυτές τις συναρτήσεις κι
αυτές εκτελούνται, παρέχοντας συ-
γκεκριμένες λειτουργίες.
ΜΠΑΡΜΠΟΥΤΙ 7

Το υποπρόγραμμα rollDice() που ακολουθεί δεν δέχεται παραμέ-


τρους (γιατί δεν χρειάζεται εξωτερικές τιμές για να λειτουργήσει),
rollDice
και επιστρέφει το άθροισμα των ενδείξεων των δύο ζαριών.
2 def rollDice():
Σχήμα 2.7: Η συνάρτηση rollDice
3 """ Ρίχνει δύο ζάρια και επιστρέφει είναι ένα ανεξάρτητο τμήμα κώδικα,
4 το άθροισμα των ενδείξεών τους. ένα αυτόνομο “εξάρτημα” που επιτελεί
5 """ μια συγκεκριμένη λειτουργία. Όταν
6 # προτροπή για ρίψη ζαριών κληθεί, η συνάρτηση προσομοιώνει
τη ρίψη δύο ζαριών κι επιστρέφει το
7 print("Ρίξε τα ζάρια με ENTER...", end="")
άθροισμα των ενδείξεών τους.
8 input()
9 # τυχαίες τιμές για τα δύο ζάρια Ο ορισμός μιας συνάρτησης ξεκινά με
τη λέξη def και ακολουθείται από το
10 dice1 = random.randint(1,6) όνομα της συνάρτησης και τις παρα-
11 dice2 = random.randint(1,6) μέτρους της, μέσα σε παρενθέσεις.
12 # υπολογισμός και εμφάνιση ζαριάς
Οι εντολές μετά την πρώτη γραμμή εί-
13 roll = dice1 + dice2 ναι στοιχισμένες δεξιότερα. Η στοί-
14 print("Έριξες", dice1, dice2, "=", roll) χιση αυτή υποδηλώνει ότι οι εντολές
15 return roll αυτές “ανήκουν” στη συνάρτηση και
θα εκτελεστούν όταν αυτή κληθεί.
Σημειώστε ότι οι μεταβλητές dice1, dice2 και roll είναι τοπικές
Το αποτέλεσμα της συνάρτησης (όταν
στο υποπρόγραμμα. Δημιουργούνται κάθε φορά που αυτό καλείται
υπάρχει) επιστρέφεται με την return.
και παύουν να υπάρχουν όταν ολοκληρωθεί η εκτέλεσή του. Η μετα-
βλητή roll του κύριου προγράμματος είναι διαφορετική από εκείνη Το ειδικό σχόλιο στην αρχή της συ-
νάρτησης, ανάμεσα στα 3 διπλά εισα-
του υποπρογράμματος. γωγικά, χρησιμοποιείται για να περι-
Προς το παρόν έχουμε μόνο ορίσει το υποπρόγραμμα. Για να το χρη- γράψει τη λειτουργία της. Είναι προ-
αιρετικό, όπως όλα τα σχόλια.
σιμοποιήσουμε, ενεργοποιώντας την εκτέλεση των εντολών του, πρέ-
πει να το καλέσουμε στα δύο σημεία του προγράμματος όπου ο χρή-
στης ρίχνει τα ζάρια, αντικαθιστώντας τις υπάρχουσες εντολές.
Έτσι, στο σημείο όπου ρίχνεται η αρχική ζαριά, τώρα καλούμε τη
rollDice(), ονομάζοντας roll το αποτέλεσμα που επιστρέφεται. rollDice
16 # ρίψη αρχικής ζαριάς
17 roll = rollDice()
18 # έλεγχος αποτελέσματος roll
19 if roll == 7 or roll == 11:
Σχήμα 2.8: Όταν κληθεί η rollDice()
Παρομοίως, στο σημείο όπου ρίχνεται η επαναλαμβανόμενη ζαριά, εκτελούνται οι εντολές της και η συ-
καλούμε και πάλι τη rollDice(), ονομάζοντας newroll το αποτέ- νάρτηση επιστρέφει μια τιμή. Για την
αρχική ζαριά, η τιμή αυτή αποδίδεται
λεσμα που επιστρέφεται.
στη μεταβλητή roll του κύριου προ-
30 # επανάληψη ρίψεων γράμματος.
31 while not over:
32 # ρίψη ζαριάς (επαναληπτική)
33 newroll = rollDice()
34 # έλεγχος αποτελέσματος
35 if newroll == roll:
craps/src/craps.5.py
ΜΠΑΡΜΠΟΥΤΙ 8

Πλήρες Πρόγραμμα
1 import random

2 def rollDice():
3 """ Ρίχνει δύο ζάρια και επιστρέφει
4 το άθροισμα των ενδείξεών τους.
5 """
6 # προτροπή για ρίψη ζαριών
7 print("Ρίξε τα ζάρια με ENTER...", end="")
8 input()
9 # τυχαίες τιμές για τα δύο ζάρια
10 dice1 = random.randint(1,6)
11 dice2 = random.randint(1,6)
12 # υπολογισμός και εμφάνιση ζαριάς
13 roll = dice1 + dice2
14 print("Έριξες", dice1, dice2, "=", roll)
15 return roll

16 # ρίψη αρχικής ζαριάς


17 roll = rollDice()
18 # έλεγχος αποτελέσματος
19 if roll == 7 or roll == 11:
20 # νίκη με την πρώτη
21 print("Κέρδισες με την πρώτη!")
22 elif roll <= 3 or roll == 12:
23 # ήττα με την πρώτη
24 print("Έχασες με την πρώτη...")
25 else:
26 # τίθεται ο "στόχος"
27 print("Ξαναρίξε. Πρέπει να φέρεις", roll)
28 # το παιχνίδι δεν έχει τελειώσει
29 over = False
30 # επανάληψη ρίψεων
31 while not over:
32 # ρίψη ζαριάς (επαναληπτική)
33 newroll = rollDice()
34 # έλεγχος αποτελέσματος
35 if newroll == roll:
36 print("Κέρδισες!")
37 # το παιχνίδι τελείωσε
38 over = True
39 elif newroll == 7:
40 print("Έχασες...")
41 # το παιχνίδι τελείωσε
42 over = True
craps/src/craps.final.py
ΜΠΑΡΜΠΟΥΤΙ 9

Ασκήσεις
2.1 Ο Ευκλείδης, στο έβδομο βιβλίο των Στοιχείων του, περιγράφει μια μέ-
θοδο για την εύρεση του μέγιστου κοινού διαιρέτη δύο ακεραίων. Τα Στοι-
χεία γράφτηκαν περίπου το 300 π.Χ. και η μέθοδος καθεαυτή θεωρείται
ακόμα παλιότερη, γι’ αυτό και ο Donald Knuth, στο εμβληματικό βιβλίο
του The Art of Computer Programming, τη χαρακτηρίζει ως τον “παπ-
πού” όλων των αλγορίθμων, γιατί είναι ο παλαιότερος γνωστός αλγόριθ-
μος που χρησιμοποιείται ακόμα και σήμερα.
Μπορεί κανείς να βρει πολλές διαφορετικές εκδοχές του αλγορίθμου,
αλλά βασικά ο αλγόριθμος μπορεί να συνοψιστεί ως εξής:

Αφαίρεσε τον μικρότερο αριθμό από τον μεγαλύτερο και επα-


νάλαβε τη διαδικασία μέχρι οι δύο αριθμοί να γίνουν ίσοι.
Η τιμή των δύο αριθμών όταν τελειώσει η διαδικασία είναι
ο μέγιστος κοινός διαιρέτης τους.

Γράψτε ένα πρόγραμμα το οποίο ζητάει από το χρήστη δύο ακέραιους


αριθμούς και υπολογίζει τον μέγιστο κοινό διαιρέτη τους, χρησιμοποιώ-
ντας τη μέθοδο του Ευκλείδη.
craps/exercises/eukleides.py

2.2 Το 1937, ο γερμανός μαθηματικός Lothar Collatz διατύπωσε τον παρα-


κάτω ισχυρισμό, ο οποίος παραμένει αναπόδεικτος:

Επιλέξτε έναν θετικό ακέραιο n. Αν είναι άρτιος διαιρέστε


τον με το 2, ενώ αν είναι περιττός πολλαπλασιάστε τον με το
3 και προσθέστε μια μονάδα. Επαναλάβετε τη διαδικασία με
τον νέο αριθμό που θα προκύψει. Από οποιονδήποτε αριθμό
n κι αν ξεκινήσετε, θα καταλήξετε στον αριθμό 1.

Η εικασία έχει επαληθευτεί αριθμητικά μέχρι και για αριθμούς της τά- xkcd.com/710/
ξης των 6 δισεκατομμυρίων δισεκατομμυρίων, ωστόσο δεν υπάρχει ανα-
λυτική μαθηματική απόδειξη. Θεωρητικά υπάρχει πάντα το ενδεχόμενο
ένας ακόμα μεγαλύτερος αριθμός να παραβιάζει την εικασία!
Γράψτε ένα πρόγραμμα το οποίο θα διαβάζει από το χρήστη τον αριθμό Για να διαπιστώσετε αν ένας αριθμός
είναι περιττός ελέγξτε αν το υπόλοιπο
εκκίνησης n, θα επαναλαμβάνει τη διαδικασία που περιγράφεται πα-
της διαίρεσής του με το 2 είναι το 1.
ραπάνω και θα εμφανίζει τους διαδοχικούς αριθμούς που προκύπτουν
από αυτή, έως ότου η διαδικασία καταλήξει στον αριθμό 1. Μπορείτε να
εμπλουτίσετε το πρόγραμμά σας, ώστε μετά το τέλος της διαδικασίας
να εμφανίζει το χρόνο τερματισμού, δηλαδή το συνολικό αριθμό των βη-
μάτων που απαιτήθηκαν, αλλά και το σημείο πλημμυρίδας, δηλαδή τον
μεγαλύτερο αριθμό που προέκυψε κατά την εκτέλεση της διαδικασίας.
Για παράδειγμα, ξεκινώντας από το n=6 δημιουργείται η παρακάτω ακο-
λουθία, με χρόνο τερματισμού τα 8 βήματα και σημείο πλημμυρίδας το 16:

6 3 10 5 16 8 4 2 1
craps/exercises/collatz.py
ΜΠΑΡΜΠΟΥΤΙ 10

2.3 Γράψτε μια συνάρτηση flip(), η οποία θα μιμείται τη ρίψη ενός νομί-
σματος, επιστρέφοντας με τυχαίο τρόπο είτε το 0, είτε το 1.
Κατασκευάστε πρόγραμμα το οποίο θα παίζει Κορώνα-Γράμματα με το
χρήστη. Το πρόγραμμα θα ρωτάει τον παίκτη αν επιλέγει Κορώνα ή Γράμ-
ματα (0 για Κορώνα και 1 για Γράμματα), θα προσομοιώνει τη ρίψη του
νομίσματος καλώντας τη flip και θα ενημερώνει τον παίκτη αν κέρ-
δισε ή έχασε. Κάντε το πρόγραμμά σας επαναληπτικό, με το παιχνίδι να
σταματά όταν ο χρήστης σε κάποιο γύρο δεν επιλέξει ούτε Κορώνα, ούτε
Γράμματα.
craps/exercises/headstails.py
Μπορείτε να τροποποιήσετε το πρόγραμμά σας έτσι ώστε να “κλέβει” το
χρήστη. Χωρίς να γίνεται ρίψη του νομίσματος, κάντε το πρόγραμμα ν’
ανακοινώνει ότι το αποτέλεσμα της ρίψης του κέρματος ήταν το αντίθετο
από αυτό που επέλεξε ο παίκτης.
craps/exercises/headstails-cheat.py
Για να γίνει το πρόγραμμα πιο πειστικό, καλό θα ήταν να μην κερδίζει
πάντα, αλλά ν’ αφήνει μερικές φορές και το χρήστη να κερδίσει και να
αισθανθεί τυχερός… Σε αυτές τις περιπτώσεις, χωρίς να γίνεται ρίψη του
νομίσματος, το πρόγραμμα θ’ ανακοινώνει ότι το αποτέλεσμα της ρίψης
του κέρματος ήταν το ίδιο με αυτό που επέλεξε ο παίκτης.
craps/exercises/headstails-cheat-probability.py
Υπόδειξη: Πριν την ανακοίνωση του “στημένου” αποτελέσματος, το πρό-
γραμμα μπορεί να επιλέγει τυχαία έναν αριθμό από το 1 μέχρι το 100. Αν
αυτός ο αριθμός ξεπερνά ένα υψηλό “κατώφλι”, τότε το πρόγραμμα θ’ ανα-
κοινώνει στον παίκτη ότι κέρδισε.
win = random.randint(1,100)
if win > 90:
# ο παίκτης κερδίζει
...
else:
# ο παίκτης χάνει
...

2.4 Να κατασκευάσετε ένα πρόγραμμα το οποίο θα παίζει επαναληπτικά το


γνωστό παιχνίδι «Πέτρα-Ψαλίδι-Χαρτί» με το χρήστη. Σε κάθε γύρο, το
πρόγραμμα θα ζητά από το χρήστη την επιλογή του (1 για την Πέτρα, 2
για το Ψαλίδι ή 3 για το Χαρτί) και στη συνέχεια θα εμφανίζει τη δική του
επιλογή και θα ανακοινώνει το νικητή του γύρου. Αν ο χρήστης πληκτρο-
λογήσει οτιδήποτε εκτός από τις τρεις έγκυρες επιλογές τότε θεωρείται
ότι επιθυμεί να τερματίσει το παιχνίδι.
craps/exercises/rps.py
Μπορείτε να δοκιμάσετε και παραλλαγές του παιχνιδιού: τροποποιήστε
το πρόγραμμα έτσι ώστε να επιλέγει από τις διαφορετικές κινήσεις με
διαφορετική πιθανότητα ή κάντε το πρόγραμμα να κλέβει (μερικές φο-
ρές), επιλέγοντας κίνηση με βάση την κίνηση του χρήστη.
craps/exercises/rps-cheat.py

2.5 Το «Ανάμεσα» ή «Acey Ducey» είναι ένα παιχνίδι με χαρτιά. Ο ένας παί-
κτης (η «μάνα») τραβάει δύο χαρτιά από την τράπουλα και τα δείχνει
στο δεύτερο παίκτη. Εκείνος με την σειρά του στοιχηματίζει ένα ποσό
ΜΠΑΡΜΠΟΥΤΙ 11

και νικάει όταν το τρίτο χαρτί είναι ανάμεσα στα δύο πρώτα. Όσο πιο
απίθανο είναι να νικήσει ένας παίκτης, τόσο μεγαλύτερο είναι το ποσό
που θα κερδίσει. Συγκεκριμένα, αν υπάρχουν d χαρτιά ανάμεσα στα δύο
πρώτα, τότε το ποσό που κερδίζει ο παίκτης σε περίπτωση νίκης θα είναι
12/d φορές το ποσό που στοιχημάτισε.
Για παράδειγμα, αν τα δύο πρώτα χαρτιά είναι το 5 και το 8, τότε ο δεύτε-
ρος παίκτης θα νικήσει αν το επόμενο χαρτί είναι το 6 ή το 7 (d=2). Στην
περίπτωση αυτή θα κερδίσει 6 φορές το ποσό που στοιχημάτισε.
Θεωρήστε ότι τα χαρτιά που χρησιμοποιούνται είναι από το 1 μέχρι και
το 13 (τα τρία τελευταία αντιστοιχούν στον βαλέ, τη ντάμα και τον ρήγα).
Αναπτύξτε πρόγραμμα το οποίο παράγει τυχαία τα δύο πρώτα χαρτιά της
μάνας. Αυτά θα πρέπει να διαφέρουν αριθμητικά τουλάχιστον κατά δύο
μονάδες, ώστε να υπάρχει περιθώριο για τουλάχιστον ένα χαρτί ανάμεσά
τους (d ≥1), αλλιώς η μάνα θα πρέπει να μοιράσει δύο νέα χαρτιά. Στη
συνέχεια, το πρόγραμμα ρωτά τον παίκτη το ποσό που επιθυμεί να στοι-
χηματίσει και παράγει το τρίτο χαρτί, εμφανίζοντας κατάλληλο μήνυμα
με το ποσό που κέρδισε ή έχασε ο δεύτερος παίκτης.
craps/exercises/aceyducey.py

Δομη Επαναληψης Η δομή επανάληψης μας δίνει τη δυνατότητα να περιγράφουμε διαδικασίες που επανα-
λαμβάνονται. Ίσως αρχικά αυτό να μην ακούγεται ιδιαίτερα εντυπωσιακό, γρήγορα όμως ανακαλύπτουμε ότι οι
περισσότερες υπολογιστικές διαδικασίες είναι εγγενώς επαναληπτικές (αυτό περιλαμβάνει και τα περισσότερα
παιχνίδια) και είναι απαραίτητος ένας συμπαγής τρόπος περιγραφής τους. Με τη δομή επανάληψης περιγρά-
φουμε τα βήματα ενός μόνο κύκλου, ακόμα κι αν τελικά αυτά τα βήματα θα εκτελεστούν πάρα πολλές φορές. ’Η
ακόμα κι αν δεν γνωρίζουμε εκ των προτέρων πόσες φορές θα εκτελεστούν. Από μία άποψη, στις υπολογιστικές
μας συσκευές ταιριάζει πολύ η δομή επανάληψης: σε αντίθεση με τους ανθρώπους, έχουν τη δυνατότητα να
εκτελούν αδιαμαρτύρητα τα ίδια βήματα, ξανά και ξανά.

Αναγνωσιμοτητα και Break Σε μια επανάληψη μπορούμε να χρησιμοποιήσουμε μία ή και περισσότερες break.
Κάθε break ανοίγει μια πόρτα εξόδου από τον κύκλο της επανάληψης. Επομένως, για να γνωρίζει κανείς πότε θα
τερματιστεί μια επανάληψη θα πρέπει να αναζητήσει τις break μέσα στην επανάληψη και να διαπιστώσει υπό
ποιες συνθήκες θα εκτελεστούν. Αντίθετα, χωρίς την break, η έξοδος από την επανάληψη γίνεται από ένα και
μοναδικό σημείο: την συνθήκη συνέχειας. Η επανάληψη τερματίζεται μόνο όταν η συνθήκη συνέχειας ελεγχθεί
και διαπιστωθεί ότι είναι ψευδής. Αρκεί λοιπόν να κοιτάξει κανείς την συνθήκη συνέχειας για να γνωρίζει πότε
θα τερματιστεί μια επανάληψη. Φαίνεται λοιπόν ότι η άμεση διακοπή μιας επανάληψης με την break είναι
μεν συχνά βολική, όμως χωρίς την break προκύπτουν προγράμματα που είναι περισσότερο κατανοητά και
ευανάγνωστα. Αυτό είναι εξαιρετικά σημαντικό για κάποιους και πολύ λιγότερο για άλλους…

Υποπρογραμματα Το μεγαλύτερο πλεονέκτημα που προκύπτει από τη χρήση υποπρογραμμάτων (και γίνεται
πολύ εμφανέστερο καθώς τα προβλήματα και τα αντίστοιχα προγράμματα μεγαλώνουν) είναι ότι είμαστε “ανα-
γκασμένοι” να αναλύουμε τα προβλήματα και να τ’ αντιμετωπίζουμε τμηματικά. Κάθε φορά εστιάζουμε την
προσοχή μας σε μικρότερα κι απλούστερα κομμάτια του γενικού προβλήματος και στη συνέχεια, συνθέτουμε τη
λύση, συναρμολογώντας τα κομμάτια αυτά. Στα κεφάλαια που ακολουθούν θα χρησιμοποιήσουμε περισσότερο
τα υποπρογράμματα και θα αναφερθούμε εκτενέστερα στα πλεονεκτήματα που πηγάζουν από τη χρήση τους.

Τυχαιοι Αριθμοι Η παραγωγή τυχαίων αριθμών έχει πολλές και σημαντικές εφαρμογές, όπως στην στατιστική,
τις προσομοιώσεις και τα παιχνίδια, όμως η σημαντικότερη εφαρμογή της είναι στην κρυπτογραφία. Υπάρχουν
αρκετές υπολογιστικές μέθοδοι για να παράγει κανείς τυχαίους αριθμούς. Όλες τους ονομάζονται και γεννήτριες
ΜΠΑΡΜΠΟΥΤΙ 12

ψευδο-τυχαίων αριθμών γιατί η αλήθεια είναι ότι οι αριθμοί που παράγουν φαίνονται τυχαίοι, αλλά δεν είναι.
Υπάρχουν μάλιστα και ειδικά στατιστικά κριτήρια που μετρούν πόσο απρόβλεπτες είναι οι ακολουθίες αριθμών
που παράγονται. Η Python (και άλλες γλώσσες) χρησιμοποιoύν έναν αλγόριθμο που ονομάζεται Mersenne-
Twister, ο οποίος είναι επαρκής για τις συνήθεις χρήσεις αλλά όχι για την κρυπτογραφία.

Ζαρια, Χαρτια και Τυχερα Παιχνιδια Πολλά από τα παραδείγματα με τα οποία θ’ ασχοληθούμε είναι παιχνίδια
με ζάρια ή χαρτιά. Τα επιλέξαμε επειδή θεωρούμε ότι είναι διασκεδαστικά και έχουν αλγοριθμικό ενδιαφέρον.
Ωστόσο, ο εθισμός σε αυτά τα παιχνίδια αποτελεί σημαντικότατο πρόβλημα. Τα ηλεκτρονικά τυχερά παιχνίδια
έχουν κι άλλες υπόγειες διαστάσεις. Μέσα από ορισμένες ασκήσεις, ελπίζουμε να κατανοήσετε πως όταν παίζεις
ένα τυχερό παιχνίδι μ’ ένα πρόγραμμα, ίσως το παιχνίδι να μην αφήνει και τόσα πολλά στην τύχη…
Μάντεψε τον Αριθμό 3
Ένα από τα πρώτα προγράμματα που συνηθίζεται να φτιάχνουν οι μαθη- 29 Αυγούστου 2016
τευόμενοι προγραμματιστές είναι ένα παιχνίδι στο οποίο ο παίκτης προ- 11:37
σπαθεί να μαντέψει τον μυστικό αριθμό που έχει “σκεφτεί” ο υπολογιστής ή
το αντίστροφο. Υπάρχουν πολλοί καλοί λόγοι που αυτό το παιχνίδι είναι
μια τόσο δημοφιλής επιλογή για τους αρχάριους: το πρόγραμμα που προ-
κύπτει δεν είναι ιδιαίτερα περίπλοκο, αν και συνδυάζει όλες τις βασικές
αλγοριθμικές έννοιες, ενώ το παιχνίδι καθεαυτό είναι πολύ διασκεδαστικό.
Έννοιες: δομή επιλογής, δομή επανάληψης, αναζήτηση.

Έχω Ένα Μυστικό


Ας ξεκινήσουμε ονομάζοντας τον μυστικό αριθμό secret και δίνο-
ντάς του μια τυχαία τιμή από το 1 μέχρι και το 32.
Εισάγουμε τη βιβλιοθήκη random για
1 import random
να χρησιμοποιήσουμε την συνάρτηση
2 # δημιουργία τυχαίου μυστικού αριθμού randint(), που παράγει τυχαίους
3 secret = random.randint(1,32) ακέραιους εντός καθορισμένων ορίων.

Τώρα θα ζητήσουμε από το χρήστη να μαντέψει τον μυστικό αριθμό.


4 # εμφάνιση προτροπής και ανάγνωση αριθμού Κάθε τιμή έχει συγκεκριμένο τύπο. Η
τιμή που επιστρέφει η input() είναι
5 print("Μάντεψε τον αριθμό: 1 - 32") αλφαριθμητική, είναι το κείμενο που
6 number = int(input()) πληκτρολόγησε ο χρήστης. Χρειάζε-
ται να μετατρέψουμε την τιμή αυτή
σε ακέραιο αριθμό, να της αλλάξουμε
Το Βρήκα; τον τύπο, και για την μετατροπή αυτή
χρησιμοποιούμε την int().
Πώς μπορώ να ελέγξω αν ο χρήστης μάντεψε τον μυστικό αριθμό;
Στην Python, οι ακέραιες τιμές εί-
Με μια δομή επιλογής θα συγκρίνουμε τον αριθμό number που έδωσε ναι τύπου int, ενώ οι αλφαριθμητι-
ο χρήστης με τον μυστικό αριθμό secret και θα εκτελέσουμε διαφο- κές είναι τύπου str. Κάθε αλφαριθμη-
ρετικές εντολές ανάλογα με το αποτέλεσμα της σύγκρισης. τική τιμή περικλείεται σε εισαγωγικά
(μονά ή διπλά). Τα μηνύματα που εμ-
7 # έλεγχος αριθμού και εμφάνιση μηνύματος φανίζουμε είναι αλφαριθμητικές τιμές,
8 if number != secret: γι’ αυτό περικλείονται σε εισαγωγικά.
9 print("Λάθος.") Με το != ελέγχεται αν δύο τιμές είναι
10 else: διαφορετικές.
11 print("Σωστά!")
guess/src/guess.1.py

1
ΜΑΝΤΕΨΕ ΤΟΝ ΑΡΙΘΜΟ 2

Αν εκτελέσετε το πρόγραμμά σας και πληκτρολογήσετε έναν αριθμό


τότε αυτό θα σας απαντήσει "Σωστά!" ή "Λάθος." Για να επαληθεύ-
σετε ότι αυτή η απάντηση είναι ορθή, θα πρέπει να γνωρίζετε τον
μυστικό αριθμό, πράγμα δύσκολο όταν αυτός καθορίζεται τυχαία.
Για να ελέγξετε λοιπόν ότι το πρόγραμμά σας λειτουργεί σωστά, θα
μπορούσατε προσωρινά να εμφανίζετε τον μυστικό αριθμό.
# ΓΙΑ ΕΛΕΓΧΟ: εμφάνιση του μυστικού αριθμού
print(secret)
Εναλλακτικά, θα μπορούσατε να ορίσετε έναν συγκεκριμένο μυστικό
αριθμό της επιλογής σας, αντί να παράγετε έναν τυχαίο:
# ΓΙΑ ΕΛΕΓΧΟ: ορισμός μυστικού αριθμού
secret = 13
Έτσι θα μπορέσετε να επιβεβαιώσετε αν το πρόγραμμά σας λειτουρ-
γεί σωστά όταν του δίνετε έναν αριθμό. Στην τελική εκδοχή του
προγράμματος, όταν οι έλεγχοι έχουν ολοκληρωθεί, θα πρέπει να
αφαιρέσετε αυτές τις εντολές.

Γύρω-Γύρω Όλοι
Πως γίνεται η διαδικασία να επαναλαμβάνεται, έτσι ώστε ο χρήστης να Η επαναληπτική δομή while συνο-
έχει περισσότερες προσπάθειες για να μαντέψει τον αριθμό; δεύεται από μια συνθήκη συνέχειας. Η
συνθήκη ελέγχεται στην αρχή κάθε
Οι εντολές που διαβάζουν έναν αριθμό από το χρήστη και τον συ- κύκλου της επανάληψης και μπορεί
γκρίνουν με τον μυστικό αριθμό θα πρέπει να τοποθετηθούν μέσα σε να είναι αληθής (True) ή ψευδής
μια επαναληπτική δομή. (False). Όσο η συνθήκη είναι αληθής,
η επανάληψη συνεχίζεται για άλλον
4 # επανάληψη: έναν κύκλο.
5 # δεν τερματίζεται, συνθήκη πάντα αληθής Μην παραλείπετε το σύμβολο : μετά
6 while True: την συνθήκη.
7 # εμφάνιση προτροπής και ανάγνωση αριθμού Οι εντολές που ακολουθούν τη while
8 print("Μάντεψε τον αριθμό: 1 - 32") είναι στοιχισμένες δεξιότερα. Η
9 number = int(input()) στοίχιση αυτή υποδηλώνει ότι αυτές
οι εντολές θα επαναλαμβάνονται.
10 # έλεγχος αριθμού και εμφάνιση μηνύματος Η πρώτη εντολή μετά τη while που
11 if number != secret: δεν θα είναι στοιχισμένη δεξιότερα
12 print("Λάθος.") δεν θα επαναλαμβάνεται, αλλά θα
13 else: εκτελεστεί μόνο μια φορά, όταν η
επανάληψη τερματιστεί.
14 print("Σωστά!")
guess/src/guess.2.py


Σταματήστε Να Κατέβω while
Δεν θα πρέπει να σταματά η επανάληψη όταν βρεθεί ο αριθμός;
Οι εντολές που τοποθετήθηκαν στην επαναληπτική δομή εκτελού-
Σχήμα 3.1: Η τετριμμένη συνθήκη
νται συνεχώς και δεν διακόπτονται ούτε καν όταν ο χρήστης μαντέ-
True που ελέγχεται από τη while εί-
ψει τον μυστικό αριθμό. Ένας απλός τρόπος να τερματιστεί η επα- ναι πάντα αληθής, κι έτσι η συγκε-
νάληψη είναι με την προσθήκη της εντολής break όταν ο παίκτης κριμένη επανάληψη δεν πρόκειται να
εντοπίσει τον μυστικό αριθμό. διακοπεί: οι εντολές της while εκτε-
λούνται επ’ άπειρον.
ΜΑΝΤΕΨΕ ΤΟΝ ΑΡΙΘΜΟ 3

# επανάληψη:
# τερματίζεται (με break) όταν βρεθεί ο αριθμός
while True: while
# εμφάνιση προτροπής και ανάγνωση αριθμού
print("Μάντεψε τον αριθμό: 1 - 32")
break
number = int(input())
# έλεγχος αριθμού και εμφάνιση μηνύματος
if number != secret: Σχήμα 3.2: Η break διακόπτει και τερ-
print("Λάθος.") ματίζει άμεσα τον κύκλο της επανά-
else: ληψης, χωρίς να ελεγχθεί η συνθήκη
print("Σωστά!") συνέχειας. Οι εντολές μετά την break
αγνοούνται και η εκτέλεση συνεχίζε-
# άμεση έξοδος από την επανάληψη ται από την πρώτη εντολή που ακο-
break λουθεί την επανάληψη.
Εφόσον το παιχνίδι θα πρέπει να συνεχίζεται όσο ο χρήστης δεν έχει Για να κατανοήσετε καλύτερα τι ση-
μαντέψει τον μυστικό αριθμό, ίσως αναρωτιέστε γιατί η συνθήκη μαίνει η άμεση διακοπή της επανά-
συνέχειας δεν έχει απλά την εξής μορφή: ληψης όταν εκτελεστεί η break, δο-
κιμάστε να τοποθετήσετε την εντολή
while number != secret: print("Σωστά!") μετά την break. Θα
διαπιστώσετε ότι το μήνυμα δεν θα εμ-
Αυτή είναι πράγματι μια εξαιρετική παρατήρηση, και θα έχετε την φανιστεί ποτέ.
ευκαιρία να τη διερευνήσετε στην άσκηση 3.2. Προς το παρόν, θα
συνεχίσουμε τροποποιώντας την τρέχουσα εκδοχή με τέτοιο τρόπο
ώστε η διακοπή της επανάληψης να μην βασίζεται στην break, αλλά
σε μια λογική μεταβλητή που θα ελέγχεται στη συνθήκη συνέχειας
της επανάληψης.
Υπάρχουν δύο λογικές τιμές: True και
4 # ο μυστικός αριθμός δεν έχει εντοπιστεί
False, οι οποίες είναι τύπου bool.
5 found = False
6 # επανάληψη: Το not χρησιμοποιείται πριν από μια
συνθήκη και αντιστρέφει την τιμή της:
7 # τερματίζεται όταν βρεθεί ο αριθμός όταν μια συνθήκη είναι ψευδής τότε η
8 while not found: αντίστροφή της είναι αληθής και το
9 # εμφάνιση προτροπής και ανάγνωση αριθμού ανάποδο.
10 print("Μάντεψε τον αριθμό: 1 - 32")
11 number = int(input())
12 # έλεγχος αριθμού και εμφάνιση μηνύματος ✗
13 if number != secret: while
14 print("Λάθος.")
15 else:
found = True
16 print("Σωστά!")
17 # ο μυστικός αριθμός εντοπίστηκε
Σχήμα 3.3: Όταν η found πάρει τιμή
18 found = True True, η επανάληψη δεν θα διακοπεί
guess/src/guess.3.py
άμεσα, γιατί η συνθήκη not found
H μεταβλητή found λειτουργεί ως σημαία και χρησιμοποιείται για της while δεν ελέγχεται συνεχώς. Η
επανάληψη θα διακοπεί όταν ελεγχθεί
να “θυμάται” το πρόγραμμά μας αν ο παίκτης έχει βρει τον μυστικό
η συνθήκη της while, δηλαδή αφού
αριθμό. Αρχικά ορίζεται ως ψευδής και όσο εξακολουθεί να είναι ολοκληρωθεί ο κύκλος της επανάλη-
ψευδής η επανάληψη συνεχίζεται. Όταν ο παίκτης βρει τον αριθμό, ψης.
η τιμή της found αλλάζει σε αληθής και η επανάληψη τερματίζε-
ται· όχι άμεσα, όπως με την break, αλλά όταν ελεγχθεί η found στη
ΜΑΝΤΕΨΕ ΤΟΝ ΑΡΙΘΜΟ 4

συνθήκη της while, δηλαδή μετά το τέλος της τρέχοντος κύκλου


της επανάληψης.

Το Μέτρημα
Το παιχνίδι δεν έχει ιδιαίτερο ενδιαφέρον αν ο χρήστης διαθέτει απεριό-
ριστες ευκαιρίες να μαντέψει τον αριθμό. Πως γίνεται να του δώσουμε
μόνο ένα μικρό πλήθος προσπαθειών;
Σε κάθε κύκλο του παιχνιδιού, θα πρέπει το πρόγραμμά μας να “γνω-
ρίζει” πόσες προσπάθειες απομένουν στο χρήστη. Αυτό είναι απα-
ραίτητο γιατί ο χρήστης θα πρέπει να ενημερώνεται σχετικά, αλλά
κυρίως επειδή το ίδιο το πρόγραμμα χρειάζεται αυτή την πληροφο-
ρία για να γνωρίζει πότε θα τερματιστεί το παιχνίδι. Θα καταμε-
τρούμε τις προσπάθειες που απομένουν με τη μεταβλητή tries.
Αρχικά, ας δώσουμε στο χρήστη ένα αυθαίρετο πλήθος προσπα-
θειών, π.χ. τέσσερις, προσθέτοντας την ακόλουθη εντολή πριν την
έναρξη της επανάληψης.
6 # ορισμός μέγιστου πλήθους προσπαθειών
7 tries = 4
Η απόδοση αρχικής τιμής σε μια μεταβλητή, όπως έγινε εδώ για
την μεταβλητή tries, ονομάζεται αρχικοποίηση. Στη συνέχεια του
παιχνιδιού, καθώς ο παίκτης προσπαθεί να μαντέψει τον αριθμό, η
τιμή της tries θα μειώνεται κατά μια μονάδα σε κάθε γύρο.
Ο τερματισμός του παιχνιδιού εξαρτάται τώρα και από το πλήθος
των προσπαθειών που απομένουν. Στην αρχή κάθε κύκλου της επα-
νάληψης θα ελέγχεται αν ο μυστικός αριθμός έχει βρεθεί, αλλά και
αν η μεταβλητή tries είναι θετική, δηλαδή αν απομένουν ακόμα
προσπάθειες. Αν κάποια από αυτές τις συνθήκες δεν ισχύει, η επα-
ναληπτική διαδικασία διακόπτεται.
Το and χρησιμοποιείται για τη σύ-
8 # επανάληψη: τερματίζεται όταν
ζευξη δύο συνθηκών. Η σύζευξη είναι
9 # βρεθεί ο αριθμός ή εξαντληθούν οι προσπάθειες αληθής μόνο όταν και οι δύο συζευγ-
10 while not found and tries > 0: μένες συνθήκες είναι αληθείς.
Σε κάθε γύρο του παιχνιδιού, ο χρήστης ενημερώνεται με ένα μήνυμα
για το πλήθος των προσπαθειών που απομένουν, ενώ στη συνέχεια
η ποσότητα αυτή (η μεταβλητή tries) μειώνεται κατά μία μονάδα.
11 # εμφάνιση και μείωση προσπαθειών
12 print("Απομένουν", tries, "προσπάθειες.")
13 tries = tries - 1
Η τελευταία εντολή μπερδεύει πολλούς αρχάριους γιατί την ερμη-
νεύουν σαν μαθηματική ισότητα, ενώ δεν είναι. Για να γίνει κατα-
νοητή, πρέπει να διαβαστεί ως εξής: υπολόγισε την τιμή της παρά-
στασης tries - 1 κι ονόμασε το αποτέλεσμα tries. Η νέα τιμή της
μεταβλητής tries υπολογίζεται με βάση την τρέχουσα τιμή της, την
οποία και αντικαθιστά.
ΜΑΝΤΕΨΕ ΤΟΝ ΑΡΙΘΜΟ 5

Προσέξτε πως όταν η tries γίνει μηδέν, η επανάληψη δεν θα τερ-


ματιστεί άμεσα, αφού η συνθήκη συνέχειας δεν ελέγχεται συνεχώς,
αλλά μόνο στην αρχή κάθε νέου κύκλου. Επομένως, ο κύκλος των
εντολών θα ολοκληρωθεί και μόνο τότε θα διαπιστωθεί ότι η tries
μηδενίστηκε, με αποτέλεσμα τη διακοπή της επανάληψης.
Όταν η επανάληψη διακοπεί, το παιχνίδι θα έχει τελειώσει. Δεν θα
γνωρίζουμε όμως για ποιο λόγο τελείωσε. Μάντεψε ο παίκτης τον
μυστικό αριθμό ή απλά εξαντλήθηκαν οι προσπάθειές του; Στη δεύ-
τερη περίπτωση ο παίκτης θα έχει χάσει και είναι απαραίτητο να
εμφανίζουμε ένα μήνυμα που θα τον ενημερώνει ποιος ήταν ο μυστι-
κός αριθμός (για να μην πιστεύει ότι τον κλέβουμε).
Οι εντολές αυτές είναι στοιχισμένες
24 # μετά την επανάληψη
πιο αριστερά από τις προηγούμενες.
25 # εμφάνιση μηνύματος αν δεν έχει βρεθεί ο αριθμός. Αυτό υποδηλώνει ότι δεν ανήκουν
26 if not found: στην επανάληψη, αντίθετα είναι οι
27 print("Ήταν ο", secret) πρώτες εντολές που θα εκτελεστούν
guess/src/guess.4.py όταν η επανάληψη διακοπεί.

Περισσότερη Πληροφορία
Θα ήθελα η απάντηση που δίνεται στο χρήστη να τον βοηθάει λίγο περισ-
σότερο να βρει τον αριθμό. Αντί να ενημερώνεται αν τον μάντεψε ή όχι,
θα μπορούσε να κατευθύνεται να ψάξει προς τα πάνω ή προς τα κάτω.
Μέχρι στιγμής, σε κάθε προσπάθεια το πρόγραμμα ελέγχει αν ο
αριθμός που έδωσε ο χρήστης ταυτίζεται με τον μυστικό αριθμό.
Τώρα θα επεκτείνουμε αυτόν τον έλεγχο, έτσι ώστε να ελέγχεται
αν ο αριθμός του χρήστη είναι μεγαλύτερος ή μικρότερος από τον
μυστικό αριθμό. Σε κάθε περίπτωση θα εμφανίζεται διαφορετικό μή-
νυμα, έτσι ώστε ο χρήστης να κατευθύνεται προς τον μυστικό αριθμό.
17 # έλεγχος αριθμού και εμφάνιση μηνύματος Το elif σημαίνει else if. Χρησιμο-
ποιείται στη δομή πολλαπλής επιλογής,
18 if number > secret: δηλαδή όταν χρειάζεται να διακρί-
19 print("Λάθος. Είναι μικρότερος.") νουμε ανάμεσα σε περισσότερες από
20 elif number < secret: δύο περιπτώσεις.
21 print("Λάθος. Είναι μεγαλύτερος.") Κάθε elif, όπως και η if, συνοδεύε-
22 else: ται από μια συνθήκη. Μετά από μια if
23 print("Σωστά!") μπορούμε να χρησιμοποιήσουμε όσες
διαδοχικές elif είναι απαραίτητες.
24 # ο μυστικός αριθμός εντοπίστηκε Οι συνθήκες ελέγχονται διαδοχικά, η
25 found = True μία μετά την άλλη, μέχρι να βρεθεί μία
guess/src/guess.5.py που να είναι αληθής, οπότε και εκτε-
λούνται οι αντίστοιχες εντολές, ενώ οι
Σκεφτείτε πόση περισσότερη πληροφορία παρέχεται στο χρήστη με συνθήκες που την ακολουθούν παρα-
αυτή την μικρή αλλαγή. Προηγουμένως, κάθε αποτυχημένη προ- κάμπτονται.
σπάθεια απέκλειε έναν από τους υποψήφιους αριθμούς. Τώρα μια
αποτυχημένη προσπάθεια αποκλείει και όλους τους αριθμούς που
είναι μικρότεροι ή μεγαλύτεροι από αυτόν που επέλεξε ο χρήστης.
ΜΑΝΤΕΨΕ ΤΟΝ ΑΡΙΘΜΟ 6

Ακόμα Περισσότερη Πληροφορία


Μετά από μερικές προσπάθειες γίνεται δύσκολο για το χρήστη να θυμά-
ται που ακριβώς έχει περιορίσει τον μυστικό αριθμό, δηλαδή από ποιον
αριθμό είναι μεγαλύτερος και από ποιον μικρότερος. Πως γίνεται αυτή
την πληροφορία να τη “θυμάται” το πρόγραμμα (αντί για τον χρήστη)
και να τον ενημερώνει κατάλληλα για να τον βοηθά στις επιλογές του;
Ο,τιδήποτε είναι ανάγκη να “θυμάται” το πρόγραμμα αποθηκεύε- low high
ται σε μεταβλητές. Θα χρησιμοποιήσουμε λοιπόν την μεταβλητή low
για την ελάχιστη τιμή που θα μπορούσε να λάβει ο μυστικός αριθμός
και την μεταβλητή high για την αντίστοιχη μέγιστη τιμή. Σχήμα 3.4: Ο μυστικός αριθμός βρί-
σκεται κάπου ανάμεσα στα όρια low
Στην αρχή του προγράμματος θα πρέπει να αποδώσουμε στις μετα- και high.
βλητές αυτές μια αρχική τιμή – θα τις αρχικοποιήσουμε.
2 # οι μεταβλητές low και high είναι τα όρια
3 # ανάμεσα στα οποία βρίσκεται ο μυστικός αριθμός
4 low = 1
5 high = 32
6 # δημιουργία τυχαίου μυστικού αριθμού
7 secret = random.randint(low,high)
Μέσα στην επανάληψη εμφανίζουμε τις τιμές αυτών των ορίων, για
να βοηθήσουμε το χρήστη στην επιλογή αριθμού.
18 # εμφάνιση προτροπής και ανάγνωση αριθμού low number high
19 print("Μάντεψε τον αριθμό:", low , "-", high)
20 number = int(input())
Σχήμα 3.5: Επιλέγεται ένας αριθμός
Θα φροντίσουμε όμως οι μεταβλητές low και high να τροποποιού- number μεταξύ των low και high.
νται κατάλληλα μετά από κάθε λανθασμένη επιλογή του χρήστη.
Συγκεκριμένα, όταν ο χρήστης επιλέξει έναν αριθμό number που εί-
ναι μεγαλύτερος από τον μυστικό, τότε η μεταβλητή high πρέπει να low high high
τροποποιηθεί κατάλληλα: ο μυστικός αριθμός δεν μπορεί να είναι
μεγαλύτερος από το number-1. Σχήμα 3.6: Αν ο μυστικός αριθμός εί-
ναι μικρότερος από το number τότε
21 # έλεγχος αριθμού και εμφάνιση μηνύματος και το άνω όριο high πρέπει να γίνει
22 if number > secret: μικρότερο από το number.
23 print("Λάθος. Είναι μικρότερος.")
24 high = number - 1
Όταν ο χρήστης επιλέξει έναν αριθμό number που είναι μικρότερος
low low high
από τον μυστικό, τότε η μεταβλητή low πρέπει να τροποποιηθεί: ο
μυστικός αριθμός δεν μπορεί να είναι μικρότερος από το number+1.
Σχήμα 3.7: Αν ο μυστικός αριθμός εί-
25 elif number < secret:
ναι μεγαλύτερος από το number τότε
26 print("Λάθος. Είναι μεγαλύτερος.") και το κάτω όριο low πρέπει να γίνει
27 low = number + 1 μεγαλύτερο από το number.
guess/src/guess.6.py
ΜΑΝΤΕΨΕ ΤΟΝ ΑΡΙΘΜΟ 7

Εξαρτήματα κι Αυτοματισμοί
Το καημένο το πρόγραμμά μου κάνει όλη τη βαρετή δουλειά. Δε γίνεται
να το αφήσω να παίξει κι αυτό λιγάκι;
Το σημείο στο οποίο ο παίκτης αλληλεπιδρά με το παιχνίδι είναι το
σημείο στο οποίο του ζητείται να μαντέψει τον μυστικό αριθμό. Ο
παίκτης ενημερώνεται για τα όρια ανάμεσα στα οποία βρίσκεται ο
μυστικός αριθμός και, με βάση αυτές τις τιμές, επιλέγει έναν αριθμό.
# εμφάνιση προτροπής και ανάγνωση αριθμού
print("Μάντεψε τον αριθμό:", low , "-", high)
number = int(input())
Ας κατασκευάσουμε λοιπόν ένα υποπρόγραμμα το οποίο θα κάνει
a b
αυτή ακριβώς τη δουλειά: θα δέχεται σαν παραμέτρους τα όρια ανά-
μεσα στα οποία βρίσκεται ο μυστικός αριθμός και θα ζητάει από το readNumber
χρήστη να επιλέξει έναν αριθμό μέσα σε αυτά τα όρια.
2 def readNumber(a,b): Σχήμα 3.8: Αναπαράσταση της συ-
3 """ ζητάει από το χρήστη έναν αριθμό νάρτησης readNumber(), η οποία δέ-
4 μεταξύ των a και b και τον επιστρέφει. χεται σαν παραμέτρους δύο ακέραιες
τιμές a και b, ζητά από το χρήστη να
5 a, b: όρια για τον αριθμό (δεν ελέγχονται) πληκτρολογήσει μια τιμή που βρίσκε-
6 """ ται ανάμεσά τους και την επιστρέφει.
7 # εμφάνιση προτροπής και ανάγνωση αριθμού
Ο ορισμός μιας συνάρτησης ξεκινά με
8 print("Μάντεψε τον αριθμό:", a , "-", b) τη δεσμευμένη λέξη def κι ακολουθεί-
9 num = int(input()) ται από το όνομα της συνάρτησης και
10 # επιστροφή αριθμού τις παραμέτρους της, σε παρενθέσεις.
11 return num Οι εντολές που ακολουθούν την
Οι μεταβλητές a, b και num είναι τοπικές και υφίστανται μόνο κα- πρώτη γραμμή είναι στοιχισμένες δε-
ξιότερα. Η στοίχιση αυτή υποδηλώνει
θόσο εκτελείται το υποπρόγραμμα. ότι οι εντολές αυτές θα εκτελεστούν
Στο σημείο του κύριου προγράμματος όπου ζητείται η είσοδος αριθ- όταν κληθεί η συνάρτηση.
μού από το χρήστη μπορούμε τώρα να καλέσουμε το υποπρόγραμμα,
low high
παρέχοντας τις κατάλληλες παραμέτρους.
28 # επιλογή αριθμού από το χρήστη
29 number = readNumber(low,high)
guess/src/guess.7.py

Κατά την κλήση αυτή, οι μεταβλητές του κύριου προγράμματος low a b


και high περνούν τις τιμές τους στις αντίστοιχες τοπικές μεταβλη- readNumber
τές a και b, όπως φαίνεται και στο σχήμα 3.9.
Ας αντικαταστήσουμε τώρα την συνάρτηση readNumber() με μια
άλλη που θα επιλέγει μόνη της έναν αριθμό, παίζοντας το ρόλο του number
χρήστη. Όμως, ποιον αριθμό θα πρέπει να επιλέγει; Εσείς, όταν δο-
κιμάζετε το πρόγραμμά σας, αναλαμβάνοντας το ρόλο του χρήστη, Σχήμα 3.9: Η κλήση της συνάρτη-
σης readNumber() από το κύριο πρό-
επιλέγετε τυχαία αριθμούς ή μήπως χρησιμοποιείτε συγκεκριμένη γραμμα. Οι τιμές των low και high
τακτική; Μπορείτε να περιγράψετε με σαφήνεια πως σκέφτεστε για χρησιμοποιούνται ως τιμές των παρα-
να επιλέξετε τον επόμενο αριθμό που θα δοκιμάσετε; μέτρων a και b. Η τιμή που επιστρέφε-
ται αποθηκεύεται στη number.
Το υποπρόγραμμα που ακολουθεί δέχεται σαν παραμέτρους τα όρια
ΜΑΝΤΕΨΕ ΤΟΝ ΑΡΙΘΜΟ 8

ανάμεσα στα οποία βρίσκεται ο μυστικός αριθμός κι επιλέγει τον


αριθμό που βρίσκεται στο μέσο του διαστήματος ανάμεσα στα όρια.
2 def midNumber(a,b):
a b
3 """ επιλέγει τον μεσαίο αριθμό
4 μεταξύ των a και b και τον επιστρέφει. midNumber
5 a, b: όρια για τον αριθμό
6 """
7 # εμφάνιση προτροπής Σχήμα 3.10: Αναπαράσταση της συ-
νάρτησης midNumber(), η οποία δέχε-
8 print("Μάντεψε τον αριθμό:", a , "-", b) ται σαν παραμέτρους δύο ακέραιες τι-
9 # υπολογισμός μεσαίου αριθμού μές a και b και επιστρέφει την τιμή που
10 num = (a + b) // 2 βρίσκεται στο μέσο τους.
11 print("Ο υπολογιστής επιλέγει:", num) Το σύμβολο // χρησιμοποιείται για
12 # επιστροφή αριθμού να υπολογιστεί το πηλίκο της ακέ-
13 return num ραιας διαίρεσης (χωρίς δεκαδικά ψη-
φία). Χρειαζόμαστε αυτή την πράξη,
Τώρα η κλήση προς τη readNumber, η οποία ζητούσε αριθμό από το αντί για την συνηθισμένη διαίρεση,
χρήστη, αντικαθίσταται με μια κλήση προς την midNumber, η οποία επειδή θέλουμε ο αριθμός number να
επιλέγει η ίδια τον αριθμό που θα ελεγχθεί. Παρατηρήστε την ευ- είναι πάντα ακέραιος.
κολία με την οποία αντικαθιστουμε το ένα “εξάρτημα” μ’ ένα άλλο,
εφόσον και τα δύο χρησιμοποιούνται για την επιλογή του επόμενου
αριθμού που θα δοκιμαστεί.
30 # επιλογή αριθμού από το ίδιο το πρόγραμμα
31 number = midNumber(low,high)
guess/src/guess.8.py

Στη φάση αυτή δεν υπάρχει πλέον είσοδος από τον χρήστη. Ο μυστι-
κός αριθμός καθορίζεται με τυχαίο τρόπο και στη συνέχεια το ίδιο
το πρόγραμμα (αναλαμβάνοντας και το ρόλο του δεύτερου παίκτη)
προσπαθεί με συστηματικό τρόπο να τον μαντέψει.

Πλήρες Τελικό Πρόγραμμα


1 import random

2 def readNumber(a,b):
3 """ ζητάει από το χρήστη έναν αριθμό
4 μεταξύ των a και b και τον επιστρέφει.
5 a, b: όρια για τον αριθμό (δεν ελέγχονται)
6 """
7 # εμφάνιση προτροπής και ανάγνωση αριθμού
8 print("Μάντεψε τον αριθμό:", a , "-", b)
9 num = int(input())
10 # επιστροφή αριθμού
11 return num
…συνεχίζεται στην επόμενη σελίδα.
ΜΑΝΤΕΨΕ ΤΟΝ ΑΡΙΘΜΟ 9

12 # οι μεταβλητές low και high είναι τα όρια


13 # ανάμεσα στα οποία βρίσκεται ο μυστικός αριθμός
14 low = 1
15 high = 32
16 # δημιουργία τυχαίου μυστικού αριθμού
17 secret = random.randint(low,high)
18 # ο μυστικός αριθμός δεν έχει εντοπιστεί
19 found = False
20 # ορισμός μέγιστου πλήθους προσπαθειών
21 tries = 4
22 # επανάληψη: τερματίζεται όταν
23 # βρεθεί ο αριθμός ή εξαντληθούν οι προσπάθειες
24 while not found and tries > 0:
25 # εμφάνιση και μείωση προσπαθειών
26 print("Απομένουν", tries, "προσπάθειες.")
27 tries = tries - 1
28 # επιλογή αριθμού από το χρήστη
29 number = readNumber(low,high)
30 # έλεγχος αριθμού και εμφάνιση μηνύματος
31 if number > secret:
32 print("Λάθος. Είναι μικρότερος.")
33 high = number - 1
34 elif number < secret:
35 print("Λάθος. Είναι μεγαλύτερος.")
36 low = number + 1
37 else:
38 print("Σωστά!")
39 # ο μυστικός αριθμός εντοπίστηκε
40 found = True
41 # μετά την επανάληψη
42 # εμφάνιση μηνύματος αν δεν έχει βρεθεί ο αριθμός.
43 if not found:
44 print("Ήταν ο",secret)
guess/src/guess.final.py

Τροποποιήσεις – Επεκτάσεις
3.1 Το τελικό πρόγραμμα ενημερώνει σε κάθε προσπάθεια για τα όρια ανά-
μεσα στα οποία βρίσκεται ο μυστικός αριθμός. Ωστόσο, ο χρήστης δεν
είναι υποχρεωμένος να εισάγει έναν αριθμό που να βρίσκεται ανάμεσα
σε αυτά τα όρια. Αν ο χρήστης δώσει έναν αριθμό εκτός ορίων τότε απλά
θα χαραμίσει μια προσπάθεια. Παράλληλα όμως, θα φανεί κι ένα πρό-
βλημα: ένα από τα όρια θα τροποποιηθεί λανθασμένα. Επιβεβαιώστε το
πρόβλημα και διορθώστε το, προσθέτοντας τις κατάλληλες εντολές.
guess/exercises/guess-limits.py

3.2 Διατυπώστε μια εναλλακτική εκδοχή του προγράμματος, στην οποία η


while θα χρησιμοποιεί τη συνθήκη number != secret για να ελέγξει
αν ο χρήστης δεν έχει βρει ακόμα τον μυστικό αριθμό και, επομένως, το
παιχνίδι πρέπει να συνεχιστεί.
ΜΑΝΤΕΨΕ ΤΟΝ ΑΡΙΘΜΟ 10

Σημειώστε ότι την πρώτη φορά που η εκτέλεση του προγράμματος φτάσει
στη while, την πρώτη φορά που θα ελεγχθεί η συνθήκη number != secret,
η μεταβλητή number θα πρέπει να έχει ήδη μια τιμή.
Μια λύση είναι να της δώσουμε αρχικά, πριν την επανάληψη, μια πλασμα-
τική τιμή, για παράδειγμα:
# πλασματική αρχική τιμή για τον αριθμό του χρήστη
number = 0
# επανάληψη: τερματίζεται όταν
# βρεθεί ο αριθμός ή εξαντληθούν οι προσπάθειες
while number != secret and tries > 0:
Έτσι η number αποκτά τιμή και ταυτόχρονα είμαστε σίγουροι ότι η συν-
θήκη number != secret θα είναι αρχικά αληθής και η επανάληψη θα ξε-
κινήσει.
Όμως μια πολύ πιο ενδιαφέρουσα λύση (και αυτή είναι η ζητούμενη εδώ)
είναι η αρχική τιμή για την number να προέρχεται από το χρήστη:
# επιλογή αριθμού από το χρήστη
number = readNumber(low,high)
# επανάληψη: τερματίζεται όταν
# βρεθεί ο αριθμός ή εξαντληθούν οι προσπάθειες
while number != secret and tries > 0:
Αυτή η προσέγγιση θα απαιτήσει σημαντική αναδιάταξη των εντολών για
να λειτουργήσει το πρόγραμμα σωστά. Αξίζει τον κόπο γιατί θα σας οδη-
γήσει σε ένα μοτίβο που είναι πολύ κοινό σε προγράμματα με επανάληψη.
guess/exercises/guess-nobreak.py

3.3 Στην τελευταία προσπάθεια, το πρόγραμμα εμφανίζει το άκομψο μήνυμα


Απομένουν 1 προσπάθειες . Επίσης, αν η τελευταία προσπάθεια εί-
ναι αποτυχημένη, το πρόγραμμα ενημερώνει τον παίκτη αν ο μυστικός
αριθμός είναι μικρότερος ή μεγαλύτερος, ενώ δεν υπάρχει πια λόγος για
κάτι τέτοιο. Φαίνεται λοιπόν ότι η τελευταία προσπάθεια είναι ξεχωρι-
στή και θα έπρεπε το πρόγραμμά μας να τη χειρίζεται με ιδιαίτερο τρόπο,
κι όχι μαζί με τις υπόλοιπες.
Τροποποιήστε το πρόγραμμα έτσι ώστε στην τελευταία προσπάθεια να
εμφανίζεται το μήνυμα Απομένει μία προσπάθεια και μετά την τε-
λευταία προσπάθεια να εμφανίζεται είτε το Σωστά! , είτε ο μυστικός
αριθμός, και τίποτε άλλο.
Δεν είναι καλή ιδέα να χρησιμοποιήσετε μια if–else μέσα στην επανά-
ληψη για να διαχωρίσετε την τελευταία προσπάθεια. Είναι προτιμότερο
να τροποποιήστε τη συνθήκη της while, έτσι ώστε η επανάληψη να συνε-
χίζεται όταν tries > 1, αντί για tries > 0. Έτσι, όταν απομένει μια προ-
σπάθεια η επανάληψη θα διακόπτεται. Προσθέστε μετά την επανάληψη τις
κατάλληλες εντολές που αφορούν την τελευταία προσπάθεια.
guess/exercises/guess-final.py
guess/exercises/guess-final-else.py

3.4 Να ορίσετε μια συνάρτηση computeNumber, η οποία επιστρέφει τον με-


σαίο αριθμό μεταξύ των παραμέτρων a και b, όπως και η συνάρτηση
midNumber, εκτός κι αν απομένει μόνο μια προσπάθεια. Στην περίπτωση
αυτή, επιστρέφει έναν τυχαίο αριθμό μεταξύ των a και b.
ΜΑΝΤΕΨΕ ΤΟΝ ΑΡΙΘΜΟ 11

Η midNumber δέχεται ως παραμέτρους μόνο τα όρια a και b για να επιστρέ-


ψει τον αριθμό που βρίσκεται ανάμεσά τους. Η computeNumber χρειάζε-
ται μια επιπλέον παράμετρο: το πλήθος των προσπαθειών που απομένουν.
Επομένως, σε σχέση με την midNumber, θα πρέπει να τροποποιήσετε κα-
τάλληλα τόσο τον ορισμό της συνάρτησης όσο και την κλήση της από το
κύριο πρόγραμμα.
Η διαφορά ανάμεσα στην αρχική midNumber και την computeNumber
που θα υλοποιήσετε εδώ αφορά μόνο την τελευταία προσπάθεια και φαί-
νεται ασήμαντη. Όμως εχει ενδιαφέρον ν’ αναρωτηθείτε τι πλεονέκτημα
μπορεί να προσφέρει η χρήση της computeNumber. Τί κερδίζει ένας παί-
κτης που χρησιμοποιεί αυτή τη μέθοδο;
guess/exercises/guess-autorandom.py
3.5 Να τροποποιήσετε το πρόγραμμα έτσι ώστε εξωτερικά, από τη σκοπιά
του χρήστη, να συμπεριφέρεται με τον ίδιο ακριβώς τρόπο αλλά στην
πραγματικότητα να μην επιλέγει μυστικό αριθμό εκ των προτέρων. Το
πρόγραμμα θα πρέπει και πάλι να διατηρεί τα όρια low και high ανά-
μεσα στα οποία ο χρήστης έχει περιορίσει τον μυστικό αριθμό. Όταν ο
χρήστης δοκιμάζει έναν νέο αριθμό, το πρόγραμμα απαντά αν ο (ανύ-
παρκτος) μυστικός είναι μικρότερος ή μεγαλύτερος, προσπαθώντας να
διατηρήσει το διάστημα ανάμεσα στα low και high όσο μεγαλύτερο γί-
νεται. Ο χρήστης ουσιαστικά κερδίζει μόνο αν τα low και high ταυτι-
στούν. Σε περίπτωση που εξαντληθούν οι προσπάθειες του παίκτη, το
πρόγραμμα θα επιλέγει εκ των υστέρων έναν αριθμό ανάμεσα στα low
και high και θ’ ανακοινώνει πως αυτός ήταν ο μυστικός αριθμός.
guess/exercises/guess-cheat.py

Ασκήσεις
3.6 Να κατασκευάσετε ένα πρόγραμμα το οποίο θα ζητά από το χρήστη έναν
αλφαριθμητικό κωδικό. Ο χρήστης θα έχει το πολύ τρεις προσπάθειες για
να εισάγει τον κωδικό του. Αν εισάγει έναν σωστό κωδικό, το πρόγραμμα
θα τον καλωσορίζει, ενώ αν εξαντλήσει ανεπιτυχώς τις προσπάθειές του,
θα εμφανίζεται ένα σχετικό μήνυμα.
guess/exercises/password.py
3.7 Να κατασκευάσετε ένα πρόγραμμα στο οποίο ο χρήστης θα σκέφτεται
έναν μυστικό αριθμό από το 1 μέχρι και το 32 και το πρόγραμμα θα προ-
σπαθεί να τον μαντέψει μέσα σε 4 προσπάθειες το πολύ. Σε κάθε προσπά-
θεια, το πρόγραμμα θα επιλέγει έναν αριθμό και θα ρωτάει το χρήστη αν
αυτός είναι ίσος, μικρότερος ή μεγαλύτερος από τον μυστικό αριθμό του.
Πρόκειται για το ίδιο παιχνίδι με το οποίο ασχοληθήκαμε σ’ αυτό το κεφά-
λαιο, αλλά με τους ρόλους παίκτη και προγράμματος αντεστραμμένους.
guess/exercises/guess-user.py
3.8 Να κατασκευάσετε ένα πρόγραμμα το οποίο θα επιλέγει έναν μυστικό
αριθμό από το 1 μέχρι και το 32 και ο χρήστης θα προσπαθεί να τον
μαντέψει μέσα σε 4 προσπάθειες το πολύ. Σε κάθε προσπάθεια, ο χρή-
στης θα επιλέγει δύο αριθμούς που θ’ αποτελούν την «παγίδα» του και
το πρόγραμμα θα τον ενημερώνει αν ο μυστικός αριθμός βρίσκεται ανά-
μεσα στους αριθμούς της παγίδας, αν είναι μικρότερος ή μεγαλύτερος
ΜΑΝΤΕΨΕ ΤΟΝ ΑΡΙΘΜΟ 12

από αυτούς. Για να βρίσκεται ένας αριθμός μέσα στην παγίδα θα πρέπει
να είναι τουλάχιστον ίσος με τον μικρότερο αριθμό της παγίδας και το
πολύ ίσος με τον μεγαλύτερο. Όταν η παγίδα αποτελείται από δύο αριθ-
μούς που είναι ίσοι μεταξύ τους και ταυτίζονται με τον μυστικό αριθμό
τότε ο χρήστης έχει μαντέψει τον μυστικό αριθμό.
guess/exercises/trap.py
Όπως και στο «Μάντεψε τον Αριθμό», μπορείτε ν’ αυτοματοποιήσετε
την αναζήτηση του αριθμού, γράφοντας μια συνάρτηση αντίστοιχη της
midNumber(), η οποία επιλέγει σε κάθε γύρο την κατάλληλη παγίδα.
guess/exercises/trap-auto.py
3.9 Να γράψετε συνάρτηση με όνομα flipBiased, η οποία θα μιμείται τη
ρίψη ενός μεροληπτικού νομίσματος και θα επιστρέφει είτε το 0, είτε
το 1. Η συνάρτηση θα δέχεται μια παράμετρο p ή οποία θ’ αντιστοιχεί
στην πιθανότητα (επί τοις 100) να επιστραφεί το 0. 1 p 1OO
high
Ένας τρόπος υλοποίησης της λειτουργίας του μεροληπτικού νομίσματος
είναι να επιλέγεται ένας τυχαίος αριθμός από το 1 μέχρι το 100. Αν αυ-
τός ο τυχαίος αριθμός δεν ξεπερνά το p, τότε επιστρέφεται το 0, αλλιώς
Αν ο τυχαίος αριθμός δεν ξεπερνά το
επιστρέφεται το 1.
p, επιστρέφεται το 0. Σε διαφορετική
Ο Jon von Neumann σκέφτηκε έναν τρόπο να προσομοιώσει κανείς τη περίπτωση, επιστρέφεται το 1.
ρίψη ενός δίκαιου νομίσματος, ακόμα κι αν έχει στη διάθεσή του ένα με-
πρώτη δεύτερη
ροληπτικό νόμισμα. Η μέθοδος που πρότεινε είναι η εξής: ρίψη ρίψη

Ρίξε το μεροληπτικό νόμισμα δύο φορές. Αν το αποτέλεσμα O O


σε κάθε ρίψη είναι διαφορετικό, τότε ανακοίνωσε το αποτέ- O 1
λεσμα της πρώτης ρίψης, αλλιώς επανάλαβε τη διαδικασία.
1 O
Το αποτέλεσμα αυτής της μεθόδου ισοδυναμεί με το αποτέλεσμα της ρί-
ψης ενός δίκαιου νομίσματος. 1 1

Να γράψετε συνάρτηση με όνομα flipFair, η οποία θα μιμείται τη ρίψη Όταν ρίχνουμε ένα νόμισμα δύο φο-
ρές υπάρχουν τέσσερα πιθανά ενδε-
ενός δίκαιου νομίσματος και θα επιστρέφει είτε το 0, είτε το 1. Η συνάρ-
χόμενα. Τα δύο από αυτά, όταν το
τηση θα πρέπει να χρησιμοποιεί τη μέθοδο του von Neumann: θα υπο- αποτέλεσμα κάθε ρίψης είναι διαφο-
λογίζει το αποτέλεσμά της χρησιμοποιώντας ένα μεροληπτικό νόμισμα, ρετικό, είναι ισοπίθανα, ακόμα κι όταν
δηλαδή καλώντας την συνάρτηση flipBiased. το νόμισμα είναι μεροληπτικό. Η μέθο-
δος του von Neumann επαναλαμβάνει
Να φτιάξετε ένα πρόγραμμα που ελέγχει αν οι συναρτήσεις flipBiased τη ρίψη δύο νομισμάτων μέχρι να προ-
και flipFair λειτουργούν όπως θα περιμέναμε. Για κάθε συνάρτηση, κύψει ένα από τα δύο ισοπίθανα ενδε-
το πρόγραμμα θα πρέπει να την καλεί επαναληπτικά και να υπολογί- χόμενα, και επιστρέφει το αποτέλεσμα
που σημειώνεται με το βελάκι, προσο-
ζει το ποσοστό των ρίψεων στις οποίες το αποτέλεσμα ήταν το 0. Αν η μοιώνοντας έτσι ένα δίκαιο νόμισμα.
flipBiased λειτουργεί σωστά, τότε το ποσοστό αυτό θα πρέπει να προ-
σεγγίζει το p. Αν η flipFair λειτουργεί σωστά, τότε το ποσοστό αυτό
θα πρέπει να προσεγγίζει το 50.
Καλό θα ήταν ο ίδιος ο χρήστης να παρέχει την τιμή της παραμέτρου p της
flipBiased και το πλήθος των επαναλήψεων. Έτσι θα μπορέσετε κι εσείς,
ως χρήστες, να πειραματιστείτε εύκολα με διάφορες τιμές. Όπως ισχύει σε
κάθε προσομοίωση, το πλήθος των επαναλήψεων πρέπει να είναι μεγάλο
(π.χ. τουλάχιστον 10000), για να είναι το αποτέλεσμα του ελέγχου αξιόπι-
στο.
guess/exercises/flip-biased.py
ΜΑΝΤΕΨΕ ΤΟΝ ΑΡΙΘΜΟ 13

Αναζητηση Όταν ψάχνουμε κάτι, όπως για παράδειγμα έναν μυστικό αριθμό, μιλάμε για αναζήτηση. Η ανα-
ζήτηση είναι κάτι που κάνουμε πολύ συχνά, είτε μόνοι μας, είτε με τη βοήθεια υπολογιστών. Ψάχνουμε για
ένα όνομα στον τηλεφωνικό κατάλογο ή για την επόμενη κίνηση σ’ ένα επιτραπέζιο παιχνίδι. Συχνά η ανα-
ζήτηση δεν είναι καθόλου απλή υπόθεση. Μπορεί ο χώρος στον οποίο ψάχνουμε να είναι αχανής. Μπορεί να
μην ξέρουμε τι ακριβώς ψάχνουμε ή πως πρέπει να το περιγράψουμε. Για παράδειγμα, η μηχανή αναζήτησης
της Google προσπαθεί μέσα σε μερικά δέκατα του δευτερολέπτου να συγκεντρώσει και να ταξινομήσει τις διευ-
θύνσεις των ιστοσελίδων που πιθανώς μας ενδιαφέρουν, βασισμένη κάθε φορά σε μερικές λέξεις-κλειδιά που
της παρέχουμε. Οι βιολόγοι αναζητούν ακολουθίες αμινοξέων μέσα στον γενετικό μας κώδικα, παρέχοντας μόνο
ένα γενικό πρότυπο γιατί μπορεί να υπάρχουν αποδεκτές παραλλαγές και μεταλλάξεις. Με δεδομένο πάντως
το πόσο σημαντικό πρόβλημα είναι η αναζήτηση, είναι ευτυχές ότι πρόκειται για ένα πρόβλημα που λύνεται
συνήθως πολύ αποδοτικά.

Δυαδικη Αναζητηση Όταν αναζητούμε ένα αντικείμενο μέσα σε ένα σύνολο από ταξινομημένα στοιχεία τότε
το καλύτερο που μπορούμε να κάνουμε είναι να ψάξουμε ακριβώς στη μέση. Αν το μεσαίο στοιχείο δεν είναι
αυτό που ψάχνουμε τότε (επειδή τα στοιχεία είναι ταξινομημένα) γνωρίζουμε προς τα που πρέπει να συνεχίσουμε
την αναζήτηση, αποκλείουμε μεμιάς τα άλλα μισά στοιχεία που βρίσκονται προς την αντίθετη κατεύθυνση και
εφαρμόζουμε την ίδια διαδικασία στα στοιχεία που απομένουν. Με τον τρόπο αυτό, είτε θα εντοπίσουμε κάποια
στιγμή το στοιχείο που αναζητούμε, είτε θα εξαντληθούν τα στοιχεία και θα φτάσουμε στο συμπέρασμα ότι αυτό
που ψάχνουμε δεν υπάρχει ανάμεσά τους. Αυτή η μέθοδος ονομάζεται δυαδική αναζήτηση και είναι ουσιαστικά
η διαδικασία που ακολουθήσαμε στο τελευταίο βήμα του παραδείγματος για να εντοπίσουμε τον μυστικό αριθμό.

Διαιρει και Βασιλευε Πρόκειται για μια στρατηγική επίλυσης προβλημάτων που στηρίζεται στον κατακερμα-
τισμό ενός προβλήματος σε μικρότερα υποπροβλήματα. Αυτά είναι συνήθως του ίδιου ή συναφούς τύπου με το
αρχικό πρόβλημα και, στην καλύτερη περίπτωση, ίδιου μεγέθους μεταξύ τους. Τα υποπροβλήματα επιλύονται
ξεχωριστά και, αν αυτό είναι απαραίτητο, οι λύσεις τους συνδυάζονται για να επιλυθεί το αρχικό πρόβλημα.
Ουσιαστικά μια παρόμοια στρατηγική ακολουθούμε όταν το πρόβλημα που μας τίθεται είναι ο εντοπισμός του
μυστικού αριθμού. Σε κάθε βήμα βρισκόμαστε αντιμέτωποι με το ίδιο πρόβλημα, αλλά τα όρια ανάμεσα στα
οποία αναζητούμε τον αριθμό ολοένα και στενεύουν. Πολλοί σημαντικοί αλγόριθμοι στην Πληροφορική βασί-
ζονται στη στρατηγική του διαίρει και βασίλευε.
Το Παιχνίδι της Αφαίρεσης 4
Στο κεφάλαιο αυτό θα φτιάξουμε ένα απλό παιχνίδι δύο παικτών μ’ ενδια- 10 Σεπτεμβρίου 2016
φέρουσα ιστορία. Στο τέλος, το πρόγραμμά μας θα συμμετέχει στο παι- 10:00
χνίδι, παίζοντας το ρόλο ενός από τους δύο παίκτες. Στην πορεία θα έχουμε
την ευκαιρία να έρθουμε ξανά σε επαφή με τις αλγοριθμικές δομές που
έχουμε συναντήσει μέχρι στιγμής, ενώ θα χρησιμοποιήσουμε υποπρογράμ-
ματα, για να κατακερματίσουμε το πρόγραμμά μας σε απλούστερα τμή-
ματα και να διαχειριστούμε την πολυπλοκότητά του.
Έννοιες: δομή επιλογής, δομή επανάληψης, υποπρογράμματα

Το παιχνίδι που θα φτιάξουμε ονομάζεται ΝΙΜ. Είναι πολύ παλιό και


πιθανότατα προέρχεται από την Κίνα. Έχει πολλές παραλλαγές κι
εδώ θ’ ασχοληθούμε με μια απλή εκδοχή του που ονομάζεται το παι-
χνίδι της αφαίρεσης. Ένα πλήθος από αντικείμενα (π.χ. σπίρτα, ξυλά-
κια) τοποθετούνται στη σειρά και ο κάθε ένας από τους δύο παίκτες
αφαιρεί με τη σειρά του από ένα μέχρι και τρία αντικείμενα, μέχρι να
μείνει μόνο ένα. Ο παίκτης που θα μείνει με το τελευταίο αντικείμενο
όταν είναι η σειρά του να παίξει χάνει το παιχνίδι. Στη γενικότερη
εκδοχή του ΝΙΜ, υπάρχουν περισσότερες σειρές από αντικείμενα.
Το ΝΙΜ είναι ένα από τα πρώτα παιχνίδια που αυτοματοποιήθη-
καν και, όπως θα δούμε στη συνέχεια, υπάρχει λόγος γι’ αυτό. Ήδη
από το 1940 η αμερικάνικη εταιρεία Westinghouse παρουσίασε ένα
μηχάνημα που έπαιζε ΝΙΜ, το Nimatron, ενώ στις αρχές της δεκα-
ετίας του ’50 εμφανίστηκαν ειδικού σκοπού ηλεκτρονικοί υπολογι-
στές που έπαιζαν ΝΙΜ, όπως ο NIMROD της βρετανικής Ferranti.

Το Στήσιμο
Ας υποθέσουμε ότι τα αντικείμενα που χρησιμοποιούν οι παίκτες είναι
σπίρτα. Με πόσα πρέπει να ξεκινήσουμε;
Οι κανόνες του παιχνιδιού δεν προσδιορίζουν το αρχικό πλήθος των
σπίρτων. Μπορούμε λοιπόν να το ορίσουμε μόνοι μας.
# αρχικό πλήθος σπίρτων
matches = 7
Εναλλακτικά, μπορούμε να αρχικοποιήσουμε τη μεταβλητή matches
με μια τυχαία τιμή (ας πούμε από το 7 μέχρι και το 21).

1
ΤΟ ΠΑΙΧΝΙΔΙ ΤΗΣ ΑΦΑΙΡΕΣΗΣ 2

1 import random
2 # αρχικό πλήθος σπίρτων
3 matches = random.randint(7,21)
Καλό θα ήταν να ενημερώσουμε το χρήστη με ένα κατάλληλο μή-
νυμα για τον αρχικό αριθμό των σπίρτων.
4 # εμφάνιση αρχικού πλήθους σπίρτων
5 print("Αρχικό πλήθος σπίρτων:", matches)

Κάνε Ένα Γύρο


Τώρα θα πρέπει να γράψω τις εντολές για έναν γύρο του παιχνιδιού και
να τις κάνω να επαναλαμβάνονται μέχρι να τελειώσει το παιχνίδι.
Το συγκεκριμένο παιχνίδι συνεχίζεται όσο απομένουν ακόμα κάποια
σπίρτα – αυτή είναι και η συνθήκη συνέχειας της επανάληψης.
6 # επανάληψη: συνεχίζεται όσο υπάρχουν σπίρτα
7 while matches > 0:
Θα ακολουθήσουν οι εντολές που είναι εμφωλευμένες στην επανα-
ληπτική δομή, δηλαδή οι εντολές που θα εκτελούνται ξανά και ξανά
σε κάθε γύρο του παιχνιδιού.
Σε κάθε γύρο θα ζητείται από έναν παίκτη το πλήθος των σπίρτων
που επιθυμεί να αφαιρέσει.
Η τιμή που επιστρέφει η input() είναι
8 # ανάγνωση σπίρτων που θα πάρει ο παίκτης
αλφαριθμητική, πρόκειται για το κεί-
9 print("Πόσα σπίρτα θέλεις;") μενο που πληκτρολόγησε ο χρήστης.
10 removed = int(input()) Χρειάζεται να μετατρέψουμε την τιμή
αυτή σε ακέραιο αριθμό, να της αλλά-
Η μεταβλητή matches θα πρέπει να μειώνεται σε κάθε κύκλο κατά ξουμε τον τύπο, και για την μετατροπή
το πλήθος των σπίρτων που αφαιρούνται από το τραπέζι. αυτή χρησιμοποιούμε την int().

11 # μείωση σπίρτων matches removed matches


12 matches = matches - removed
Κάτι ακόμα που χρειάζεται σε κάθε κύκλο του παιχνιδιού είναι να 15 2 15 13
εμφανίζουμε στους παίκτες την τιμή της μεταβλητής matches, ώστε
-
να αποφασίζουν για το πλήθος των σπίρτων που θα αφαιρέσουν.
13 # εμφάνιση πλήθους σπίρτων που απομένουν (α) (β)
14 print("Σπίρτα που απομένουν:", matches)
nim/src/nim.1.py Σχήμα 4.1: Παράδειγμα μείωσης
του πλήθους των σπίρτων: (α) υπο-
λογίζεται η τιμή της παράστασης
Ποιός Παίζει; matches - removed, δηλαδή το πλή-
θος των σπίρτων που θ’ απομείνουν
Προς το παρόν δεν υπάρχει διαφοροποίηση ανάμεσα στους παίκτες. Δεν στο τραπέζι αφού αφαιρεθούν τα
ξέρουμε ούτε ποιος παίζει κάθε φορά, ούτε ποιος νικάει. σπίρτα που επιλέγει ο παίκτης και (β)
η τιμή αυτή αποτελεί τη νέα τιμή της
Σε κάθε κύκλο του παιχνιδιού, το πρόγραμμα ρωτάει πόσα σπίρτα matches.
θα αφαιρεθούν. Ωστόσο, δεν καταγράφει ποιος από τους δύο παί-
κτες είναι που αφαιρεί κάθε φορά τα σπίρτα κι έτσι δεν είναι σε
θέση να υπολογίσει ποιος είναι ο νικητής όταν αυτά τελειώσουν.
ΤΟ ΠΑΙΧΝΙΔΙ ΤΗΣ ΑΦΑΙΡΕΣΗΣ 3

Υπάρχουν αρκετοί τρόποι να λύσουμε αυτό το πρόβλημα. Μια απλή


player player
προσέγγιση είναι να χρησιμοποιήσουμε μια μεταβλητή player, η
οποία σε κάθε γύρο θα παίρνει εναλλάξ την τιμή 1 ή 2, υποδεικνύο-
ντας με αυτόν τον τρόπο ποιος παίκτης έχει σειρά να παίξει. 1 2 1 2

Αρχικά, πριν ξεκινήσει η διαδικασία του παιχνιδιού, ορίζουμε ποιος Σχήμα 4.2: Η τιμή της μεταβλητής
παίκτης ξεκινάει πρώτος: αυτός θα είναι πάντα ο παίκτης με αριθμό 1. player εναλλάσσεται σε κάθε γύρο
μεταξύ του 1 και του 2, υποδεικνύο-
6 # ορισμός παίκτη που θα παίξει πρώτος ντας τον αριθμό του παίκτη που έχει
σειρά να παίξει. Η αρχική της τιμή εί-
7 player = 1
ναι το 1.
Τώρα πλέον η προτροπή που εμφανίζεται σε κάθε κύκλο απευθύνε-
ται σε συγκεκριμένο παίκτη:
10 # ανάγνωση σπίρτων που θα πάρει ο παίκτης
11 print("Παίκτη", player, "πόσα σπίρτα θέλεις;")
12 removed = int(input())
nim/src/nim.2.py

Πριν τελειώσει κάθε κύκλος της επανάληψης, η τιμή της μεταβλη-


τής player θα πρέπει να τροποποιείται, ώστε να υποδεικνύει τον
επόμενο παίκτη που έχει σειρά να παίξει.
Για τον υπολογισμό του επόμενου παίκτη θα χρησιμοποιήσουμε ένα
υποπρόγραμμα, το οποίο δέχεται σαν παράμετρο τον αριθμό p ενός
παίκτη και επιστρέφει τον αριθμό του επόμενου παίκτη.
2 def next(p): p
3 """ Επιστρέφει τον αριθμό του παίκτη
next
4 που παίζει μετά τον παίκτη p.
5 p: αριθμός παίκτη (1 ή 2)
6 """ Σχήμα 4.3: Αναπαράσταση της συ-
7 if p == 1: νάρτησης next, η οποία δέχεται σαν
8 return 2 παράμετρο τον αριθμό p ένος παίκτη
9 else: και επιστρέφει τον αριθμό του παίκτη
που παίζει μετά τον παίκτη p.
10 return 1
Το υποπρόγραμμα δεν τροποποιεί την τιμή της μεταβλητής player, player player
απλά επιστρέφει την επόμενη τιμή της. Στο κύριο πρόγραμμα, στο
τέλος της επανάληψης, καλείται το υποπρόγραμμα και η τιμή που 1 1 2
επιστρέφει, δηλαδή ο αριθμός του επόμενου παίκτη, γίνεται η νέα
τιμή της μεταβλητής player. p
26 # εναλλαγή παίκτη
27 player = next(player)
Η χρήση του υποπρογράμματος στην εναλλαγή του παίκτη διατη-
ρεί τον κώδικά μας σύντομο και ευανάγνωστο. Με μία και μοναδική (α) (β)
γραμμή καθορίζουμε ότι η τιμή της player θα μεταβληθεί ώστε ν’
Σχήμα 4.4: Παράδειγμα εναλλαγής
αντιστοιχεί στον επόμενο παίκτη. Το πως θα γίνει αυτό καθορίζε- παίκτη: στο τέλος κάθε γύρου: (α) κα-
ται εντός του υποπρογράμματος και δεν επηρρεάζει το σημείο όπου λείται η συνάρτηση next, με παράμε-
καλείται το υποπρόγραμμα. τρο την τιμή της μεταβλητής player.
H next επιστρέφει μια τιμή που αντι-
στοιχεί στον αριθμό του παίκτη που
έχει σειρά να παίξει στον επόμενο
γύρο και (β) αυτή η τιμή γίνεται η νέα
τιμή της player.
ΤΟ ΠΑΙΧΝΙΔΙ ΤΗΣ ΑΦΑΙΡΕΣΗΣ 4

Πράγματι, δεν υπάρχει μόνο ένας τρόπος να υλοποιηθεί η next. Στον


κώδικα που ακολουθεί δίνεται μια εναλλακτική προσέγγιση. Ανε-
ξάρτητα από την εκδοχή της next που θα επιλέξουμε να χρησιμο-
ποιήσουμε, η κλήση της θα παραμείνει η ίδια.
Η έκφραση p % 2 υπολογίζει το υπό-
def next(p):
λοιπο της ακέραιας διαίρεσης του p με
""" Επιστρέφει τον αριθμό του παίκτη το 2. Η τιμή της μπορεί να είναι 0 ή 1.
που παίζει μετά τον παίκτη p.
p: αριθμός παίκτη (1 ή 2)
"""
return (p % 2) + 1
Όταν η επανάληψη τελειώσει, η μεταβλητή player δείχνει ποιος
παίκτης θα είχε σειρά να παίξει μετά τον παίκτη που πήρε τα τε-
λευταία σπίρτα. Συνεπώς, μπορούμε να χρησιμοποιήσουμε τη μετα-
βλητή player για να διαπιστώσουμε ποιος παίκτης κέρδισε.
Δεν υπάρχει εσοχή πριν την εντολή
28 # εμφάνιση αποτελέσματος παιχνιδιού
που εμφανίζει το αποτέλεσμα του παι-
29 print("Παίκτη", player, "κέρδισες!") χνιδιού. Η εντολή είναι στοιχισμένη
nim/src/nim.3.py πιο αριστερά απ’ τις προηγούμενες κι
αυτό υποδηλώνει ότι δεν ανήκει στην
επανάληψη. Αντίθετα είναι η πρώτη
Μη Λέμε κι Ό,τι Θέλουμε εντολή που θα εκτελεστεί όταν η επα-
Το πρόγραμμά μας επιτρέπει στους παίκτες να αφαιρέσουν σ’ έναν γύρο νάληψη διακοπεί.
όσα σπίρτα θέλουν!
Σύμφωνα με τους κανόνες του παιχνιδιού, ένας παίκτης επιτρέπεται
να αφαιρέσει από ένα μέχρι και τρία σπίρτα κάθε φορά, αρκεί τα
σπίρτα που έχουν απομείνει στο τραπέζι να είναι περισσότερα από
τρία. Σε διαφορετική περίπτωση, επιτρέπεται να αφαιρέσει το πολύ
όσα σπίρτα έχουν απομείνει.
Θα φτιάξουμε ένα υποπρόγραμμα που δέχεται σαν παράμετρο το
πλήθος των σπίρτων που έχουν απομείνει κι επιστρέφει το μέγιστο
πλήθος σπίρτων που επιτρέπεται ν’ αφαιρεθούν. Χρειαζόμαστε αυτό
το άνω όριο, για να μπορούμε να ελέγξουμε παρακάτω αν το πλήθος
των σπίρτων που επιθυμεί ν’ αφαιρέσει ο παίκτης είναι έγκυρο.
11 def maxMatches(m):
12 """ Επιστρέφει το μέγιστο πλήθος σπίρτων
13 που επιτρέπεται να αφαιρεθούν.
14 m: πλήθος σπίρτων που απομένουν
15 """
16 # το πολύ 3 σπίρτα ή όσα απομένουν
17 if m > 3:
18 return 3
19 else:
20 return m
Τώρα μπορούμε να κατασκευάσουμε ένα υποπρόγραμμα το οποίο
διαβάζει από τον παίκτη το πλήθος των σπίρτων που επιθυμεί ν’
αφαιρέσει, ελέγχει την τιμή που δίνει ο παίκτης κι εξασφαλίζει ότι
αυτή δεν παραβιάζει τους κανόνες.
ΤΟ ΠΑΙΧΝΙΔΙ ΤΗΣ ΑΦΑΙΡΕΣΗΣ 5

Το υποπρόγραμμα δέχεται ως παραμέτρους τον αριθμό p του παί-


κτη που έχει σειρά να παίξει (για να εμφανίσει την κατάλληλη προ-
τροπή) και το πλήθος m των σπίρτων που απομένουν (για να κάνει
τους απαραίτητους ελέγχους).
21 def readMatches(p,m):
22 """ Διαβάζει από το χρήστη κι επιστρέφει
23 το πλήθος σπίρτων που θα αφαιρεθούν.
24 Εξασφαλίζει ότι η τιμή είναι έγκυρη.
25 p: αριθμός παίκτη που παίζει
26 m: πλήθος σπίρτων που απομένουν
27 """
Αρχικά ζητείται από τον παίκτη να πληκτρολογήσει το πλήθος των
σπίρτων που επιθυμεί να αφαιρέσει, έχοντας ήδη υπολογίσει το μέ-
γιστο επιτρεπόμενο πλήθος, με βάση τα σπίρτα που απομένουν:
28 # μέγιστο πλήθος σπίρτων προς αφαίρεση
29 limit = maxMatches(m)
30 # ανάγνωση σπίρτων που θα πάρει ο παίκτης
31 print("Παίκτη", p, "πόσα σπίρτα θέλεις;")
32 num = int(input())
Όσο η τιμή που πληκτρολογείται από τον παίκτη δεν είναι έγκυρη
(μέσα στα επιτρεπόμενα όρια), εμφανίζεται σχετικό μήνυμα και η
ανάγνωση τιμής επαναλαμβάνεται.
33 # έλεγχος και επανάληψη (σε περίπτωση λάθους)
34 while num < 1 or num > limit:
35 # μήνυμα λάθους
36 print("Πάρε από 1 μέχρι και",limit,"σπίρτα.")
37 # ανάγνωση σπίρτων που θα πάρει ο παίκτης
38 print("Παίκτη", p, "πόσα σπίρτα θέλεις;")
39 num = int(input())
Γιατί πρέπει ο έλεγχος να επαναλαμβάνεται; Γιατί να μην ελεγxθεί η
τιμή που δίνει ο παίκτης με μια απλή δομή επιλογής; Πρέπει να σκε-
φτούμε ότι ο χρήστης μπορεί να πληκτρολογεί συνεχώς τιμές που δεν
είναι έγκυρες. Χρειαζόμαστε λοιπόν την επανάληψη ώστε η ανά-
γνωση τιμής να εγκλωβιστεί σ’ έναν κύκλο, ο οποίος διακόπτεται
μόνο όταν ο χρήστης δώσει έγκυρη τιμή.
Όταν ολοκληρωθεί η επανάληψη, θα έχουμε εξασφαλίσει ότι η τιμή
που θα έχει διαβαστεί από το χρήστη, η τιμή της num, θα είναι έγκυρη.
Μπορούμε λοιπόν πλέον να επιστρέψουμε αυτή την τιμή, ως το απο-
τέλεσμα του υποπρογράμματος.
40 # επιστροφή τιμής
41 return num
Τώρα, στο κύριο πρόγραμμα μπορούμε να αντικαταστήσουμε την
εντολή εισόδου που διαβάζει χωρίς έλεγχο τον αριθμό των σπίρτων,
με την κλήση του υποπρογράμματος που κατασκευάσαμε.
ΤΟ ΠΑΙΧΝΙΔΙ ΤΗΣ ΑΦΑΙΡΕΣΗΣ 6

52 # ανάγνωση σπίρτων που θα πάρει ο παίκτης


53 removed = readMatches(player, matches)
nim/src/nim.4.py

Αν δεν είχαμε κατασκευάσει το υποπρόγραμμα, θα είχαμε τοποθε-


τήσει όλον τον κώδικα που υλοποιεί τους απαραίτητους ελέγχους
μέσα στο κύριο πρόγραμμα, αυξάνοντας έτσι σημαντικά την πολυ-
πλοκότητά του.

Χαζό Μηχάνημα
Βαριέμαι να παίζω μόνος μου. Δεν γίνεται να παίζω με αντίπαλο το πρό-
γραμμά μου;
Το πρόγραμμά μας θα πρέπει να αποφασίζει για το πλήθος των
σπίρτων που θα αφαιρέσει. Δεν είναι ανάγκη να καταλήξουμε από
την αρχή σε κάποια ευφυή στρατηγική, μπορούμε να ξεκινήσουμε
με μια τυχαία επιλογή σπίρτων.
Θα κατασκευάσουμε ένα απλό υποπρόγραμμα το οποίο δέχεται σαν
παράμετρο το πλήθος των σπίρτων που έχουν απομείνει και επιλέγει
τυχαία να αφαιρέσει από 1 μέχρι το μέγιστο πλήθος σπίρτων που
επιτρέπεται να αφαιρεθούν.
Οι παράμετροι που χρησιμοποιούνται
42 def randomMatches(m):
κατά την κλήση μιας συνάρτησης δεν
43 """ Επιλέγει κι επιστρέφει ένα τυχαίο είναι μόνο σταθερές ή μεταβλητές,
44 αλλά έγκυρο πλήθος σπίρτων που θα αφαιρεθούν. αλλά κι ολόκληρες εκφράσεις, που
45 m: πλήθος σπίρτων που απομένουν μπορεί να περιλαμβάνουν κλήσεις συ-
46 """ ναρτήσεων, όπως εδώ η maxMatches.
Στο παράδειγμα αυτό, πρώτα καλείται
47 return random.randint(1,maxMatches(m)) η maxMatches και η τιμή που επιστρέ-
Στην τελευταία γραμμή, η τιμή που επιστρέφει η maxMatches() χρη- φει χρησιμοποιείται σαν δεύτερη πα-
ράμετρος στην κλήση της randint.
σιμοποιείται ως άνω όριο για τον τυχαίο αριθμό που θα δημιουργη-
θεί. Είναι σχεδόν το ίδιο με τον κώδικα που ακολουθεί, αλλά χωρίς
την ενδιάμεση μεταβλητή limit.
limit = maxMatches(m)
return random.randint(1, limit)
Είναι ενδιαφέρον ότι η συνάρτηση maxMatches() χρησιμοποιείται
τώρα και από την readMatches() για την ανάγνωση αριθμού από
τον χρήστη, αλλά και από την randomMatches() για την επιλογή
ενός αριθμού σπίρτων από το ίδιο το πρόγραμμα. Αποδεικνύεται
έτσι ένα χρήσιμο “εξάρτημα” που επαναχρησιμοποιείται σε διαφορε-
τικά σημεία του προγράμματος για διαφορετικούς λόγους.
Ας εστιάσουμε τώρα στο κύριο πρόγραμμα, όπου θα πρέπει να ει-
σάγουμε τις απαραίτητες επεκτάσεις ώστε το ίδιο το πρόγραμμα να
αναλαμβάνει πλέον το ρόλο ενός από τους δύο παίκτες. Θα χρησι-
μοποιήσουμε τη μεταβλητή computer, της οποίας η τιμή θ’ αντιστοι-
χεί στον αριθμό του παίκτη που θα παίζει αυτοματοποιημένα. Στην
αρχή του παιχνιδιού η computer θα παίρνει τυχαία την τιμή 1 ή 2.
ΤΟ ΠΑΙΧΝΙΔΙ ΤΗΣ ΑΦΑΙΡΕΣΗΣ 7

52 # επιλογή παίκτη-υπολογιστή
53 computer = random.randint(1,2)
Θυμηθείτε ότι η μεταβλητή player εναλλάσσεται σε κάθε γύρο με-
ταξύ των τιμών 1 και 2, υποδεικνύοντας ποιος παίκτης έχει σειρά
να παίξει στον επόμενο γύρο. Συκρίνοντας λοιπόν την player με
την computer ελέγχουμε ουσιαστικά αν ο επόμενος παίκτης είναι ο
άνθρωπος ή το πρόγραμμά μας, ώστε να καλέσουμε σε κάθε περί-
πτωση την ανάλογη συνάρτηση που θα μας επιστρέψει το πλήθος
των σπίρτων που θα αφαιρεθούν.
player player
58 # επιλογή κίνησης, ανάλογα με τον παίκτη
59 if player == computer:
60 # σπίρτα που θα πάρει ο υπολογιστής
61 removed = randomMatches(matches) computer computer
62 print("Ο υπολογιστής παίρνει", removed)
(α) (β)
63 else:
64 # ανάγνωση σπίρτων που θα πάρει ο παίκτης Σχήμα 4.5: Επιλογή κίνησης, ανάλογα
65 removed = readMatches(player, matches) με τον παίκτη: (α) αν η computer έχει
ίδια τιμή με την player, τότε είναι
Χρειάζεται να φροντίσουμε μια ακόμα σημαντική λεπτομέρεια. Μέ- σειρά του προγράμματος να παίξει,
χρι στιγμής, στο τέλος του παιχνιδιού ανακοινώνεται ο αριθμός του ενώ (β) σε διαφορετική περίπτωση,
έχει σειρά να παίξει ο χρήστης.
παίκτη που κέρδισε. Τώρα που μόνο ο ένας παίκτης είναι άνθρωπος,
είναι προτιμότερο να εμφανίζουμε διαφοροποιημένα μηνύματα.
72 # εμφάνιση αποτελέσματος παιχνιδιού
73 if player == computer:
74 print("Κέρδισε ο υπολογιστής.")
75 else:
76 print("Παίκτη", player, "κέρδισες!")
nim/src/nim.5.py

Άνθρωπος Εναντίον Μηχανής


Όταν το πρόγραμμά μου παίζει τυχαία δεν έχει και μεγάλο ενδιαφέρον.
Δεν γίνεται να το κάνουμε λίγο πιο “έξυπνο”;
Θα πρέπει πρώτα εμείς να σχεδιάσουμε έναν καλύτερο τρόπο παι-
χνιδιού και μετά να τον περιγράψουμε με τις κατάλληλες εντολές.
Είναι ευκολότερο ν’ αρχίσουμε μελετώντας ποιες είναι οι ενδεδειγ-
μένες κινήσεις όταν απομένουν 2, 3 ή 4 σπίρτα: ο παίκτης που έχει
σειρά να παίξει μπορεί να αφαιρέσει αντίστοιχα 1, 2 ή 3 σπίρτα
και να κερδίσει άμεσα. Αντίθετα, όταν απομένουν 5 σπίρτα η κατά-
σταση είναι δύσκολη: όσα σπίρτα και ν’ αφαιρέσει ο παίκτης που
έχει σειρά να παίξει, η έκβαση είναι στα χέρια του αντιπάλου.
Ανάλογες δυσάρεστες καταστάσεις, που θα ονομάσουμε «ανεπιθύ-
μητες νησίδες», αντιμετωπίζει ο παίκτης που έχει σειρά να παίξει
όταν απομένουν 9, 13, 17, κ.ο.κ σπίρτα. Όσα σπίρτα κι αν αφαιρέ-
σει, ο αντίπαλος μπορεί να τον στείλει στην επόμενη νησίδα και να
τον αναγκάσει να χάσει (σχήμα 4.6).
ΤΟ ΠΑΙΧΝΙΔΙ ΤΗΣ ΑΦΑΙΡΕΣΗΣ 8

σπίρτα m σπίρτα που πρέπει σπίρτα 1


που απομένουν ν’ αφαιρέσει που θ’ απομείνουν
στον παίκτη ο παίκτης στον αντίπαλο
2
2 1 1
3 2 1 3
4 3 1
4
5 αδιάφορο
6 1 5 5
7 2 5
6
8 3 5
9 αδιάφορο 7
10 1 9
8

9
Στον πίνακα υπάρχει εμφανής περιοδικότητα: οι ενδεδειγμένες κινή-
σεις επαναλαμβάνονται ανά τέσσερα σπίρτα. Αυτό είναι ένδειξη ότι
για να υπολογίσουμε το πλήθος των σπίρτων που θα πρέπει ν’ αφαι- 10
ρεθούν, θα πρέπει να χρησιμοποιήσουμε το υπόλοιπο της ακέραιας
διαίρεσης του πλήθους των σπίρτων m με το 4. Η δεύτερη στήλη του Σχήμα 4.6: Οι κύκλοι αντιστοιχούν
πίνακα που ακολουθεί περιέχει την τιμή της έκφρασης m % 4. στις διαφορετικές καταστάσεις του
παιχνιδιού: ο αριθμός κάθε κύκλου εί-
σπίρτα m τιμή τιμή σπίρτα που πρέπει ναι τα σπίρτα που απομένουν σε κάθε
που απομένουν έκφρασης ν’ αφαιρέσει κατάσταση. Οι καταστάσεις 1, 5, 9, …
έκφρασης
είναι οι «ανεπιθύμητες νησίδες»: όταν
στον παίκτη m%4 (m - 1) % 4 ο παίκτης
ένας παίκτης βρίσκεται σε μια από
2 2 1 1 αυτές τότε ο αντίπαλός του μπορεί
πάντα να τον οδηγήσει στην επόμενη
3 3 2 2 νησίδα και, τελικά, στην ήττα.

4 0 3 3
5 1 0 αδιάφορο
6 2 1 1
7 3 2 2
8 0 3 3
9 1 0 αδιάφορο
10 2 1 1

Το υποπρόγραμμα που ακολουθεί δέχεται σαν παράμετρο το πλήθος


m των σπίρτων που απομένουν (πρώτη στήλη), υπολογίζει την τιμή
της έκφρασης m % 4 (δεύτερη στήλη) και, με βάση την αντιστοιχία
του πίνακα, επιστρέφει το πλήθος των σπίρτων που χρειάζεται ν’
ΤΟ ΠΑΙΧΝΙΔΙ ΤΗΣ ΑΦΑΙΡΕΣΗΣ 9

αφαιρεθούν ώστε ο αντίπαλος να οδηγηθεί σε μια ανεπιθύμητη νη-


σίδα (τέταρτη στήλη). Αν το πλήθος m αντιστοιχεί σε ανεπιθύμητη
νησίδα, το υποπρόγραμμα επιστρέφει έναν τυχαίο αριθμό σπίρτων.
def computeMatches(m):
""" Επιλέγει κι επιστρέφει το βέλτιστο
πλήθος σπίρτων που θα πρέπει να αφαιρεθούν.
Αν δεν υπάρχει, επιστρέφει μια τυχαία τιμή.
m: πλήθος σπίρτων που απομένουν
"""
# υπολογισμός υπολοίπου
mod = m % 4
if mod == 0:
return 3
elif mod == 1:
# ανεπιθύμητη νησίδα: τυχαία κίνηση
return randomMatches(m)
elif mod == 2:
return 1
else:
return 2
Θα μπορούσαμε να υπολογίσουμε πιο άμεσα το πλήθος των σπίρτων
που χρειάζεται ν’ αφαιρεθούν σε κάθε περίπτωση; Ουσιαστικά, θα
θέλαμε να υπολογίσουμε ποια είναι η απόσταση από την κοντινότερη
ανεπιθύμητη νησίδα ή, με άλλα λόγια, πόσα σπίρτα περισσεύουν σε
σχέση με την πλησιέστερη νησίδα, ώστε να τα αφαιρέσουμε.
Μελετώντας τον προηγούμενο πίνακα, προκύπτει ότι η έκφραση που
δίνει αυτή την απόσταση είναι η (m - 1) % 4, της οποίας η τιμή
δίνεται στην τρίτη στήλη. Παρατηρήστε πως οι τιμές στην τρίτη
και την τέταρτη στήλη ουσιαστικά ταυτίζονται. Οδηγούμαστε έτσι
σε μια κομψότερη εναλλακτική υλοποίηση του υποπρογράμματος:
48 def computeMatches(m):
49 """ Επιλέγει κι επιστρέφει το βέλτιστο
50 πλήθος σπίρτων που θα πρέπει να αφαιρεθούν.
51 Αν δεν υπάρχει, επιστρέφει μια τυχαία τιμή.
52 m: πλήθος σπίρτων που απομένουν
53 """
54 # υπολογισμός υπολοίπου
55 mod = (m - 1) % 4
56 if mod == 0:
57 # ανεπιθύμητη νησίδα: τυχαία κίνηση
58 return randomMatches(m)
59 else:
60 return mod
Στο κύριο πρόγραμμα, η κλήση της randomMatches() για την επι-
λογή ενός τυχαίου πλήθους σπίρτων πρέπει τώρα να αντικαταστα-
θεί από την κλήση της computeMatches().
ΤΟ ΠΑΙΧΝΙΔΙ ΤΗΣ ΑΦΑΙΡΕΣΗΣ 10

73 # σπίρτα που θα πάρει ο υπολογιστής


74 removed = computeMatches(matches)
75 print("Ο υπολογιστής παίρνει", removed)
nim/src/nim.6.py

Πλήρες Τελικό Πρόγραμμα


1 import random
2 def next(p):
3 """ Επιστρέφει τον αριθμό του παίκτη
4 που παίζει μετά τον παίκτη p.
5 p: αριθμός παίκτη (1 ή 2)
6 """
7 if p == 1:
8 return 2
9 else:
10 return 1
11 def maxMatches(m):
12 """ Επιστρέφει το μέγιστο πλήθος σπίρτων
13 που επιτρέπεται να αφαιρεθούν.
14 m: πλήθος σπίρτων που απομένουν
15 """
16 # το πολύ 3 σπίρτα ή όσα απομένουν
17 if m > 3:
18 return 3
19 else:
20 return m
21 def readMatches(p,m):
22 """ Διαβάζει από το χρήστη κι επιστρέφει
23 το πλήθος σπίρτων που θα αφαιρεθούν.
24 Εξασφαλίζει ότι η τιμή είναι έγκυρη.
25 p: αριθμός παίκτη που παίζει
26 m: πλήθος σπίρτων που απομένουν
27 """
28 # μέγιστο πλήθος σπίρτων προς αφαίρεση
29 limit = maxMatches(m)
30 # ανάγνωση σπίρτων που θα πάρει ο παίκτης
31 print("Παίκτη", p, "πόσα σπίρτα θέλεις;")
32 num = int(input())
33 # έλεγχος και επανάληψη (σε περίπτωση λάθους)
34 while num < 1 or num > limit:
35 # μήνυμα λάθους
36 print("Πάρε από 1 μέχρι και",limit,"σπίρτα.")
37 # ανάγνωση σπίρτων που θα πάρει ο παίκτης
38 print("Παίκτη", p, "πόσα σπίρτα θέλεις;")
39 num = int(input())
40 # επιστροφή τιμής
41 return num
ΤΟ ΠΑΙΧΝΙΔΙ ΤΗΣ ΑΦΑΙΡΕΣΗΣ 11

42 def randomMatches(m):
43 """ Επιλέγει κι επιστρέφει ένα τυχαίο
44 αλλά έγκυρο πλήθος σπίρτων που θα αφαιρεθούν.
45 m: πλήθος σπίρτων που απομένουν
46 """
47 return random.randint(1,maxMatches(m))
48 def computeMatches(m):
49 """ Επιλέγει κι επιστρέφει το βέλτιστο
50 πλήθος σπίρτων που θα πρέπει να αφαιρεθούν.
51 Αν δεν υπάρχει, επιστρέφει μια τυχαία τιμή.
52 m: πλήθος σπίρτων που απομένουν
53 """
54 # υπολογισμός υπολοίπου
55 mod = (m - 1) % 4
56 if mod == 0:
57 # ανεπιθύμητη νησίδα: τυχαία κίνηση
58 return randomMatches(m)
59 else:
60 return mod
61 # αρχικό πλήθος σπίρτων
62 matches = random.randint(7,21)
63 # εμφάνιση αρχικού πλήθους σπίρτων
64 print("Αρχικό πλήθος σπίρτων:", matches)
65 # επιλογή παίκτη-υπολογιστή
66 computer = random.randint(1,2)
67 # ορισμός παίκτη που θα παίξει πρώτος
68 player = 1
69 # επανάληψη: συνεχίζεται όσο υπάρχουν σπίρτα
70 while matches > 0:
71 # επιλογή κίνησης, ανάλογα με τον παίκτη
72 if player == computer:
73 # σπίρτα που θα πάρει ο υπολογιστής
74 removed = computeMatches(matches)
75 print("Ο υπολογιστής παίρνει", removed)
76 else:
77 # ανάγνωση σπίρτων που θα πάρει ο παίκτης
78 removed = readMatches(player, matches)
79 # μείωση σπίρτων
80 matches = matches - removed
81 # εμφάνιση πλήθους σπίρτων που απομένουν
82 print("Σπίρτα που απομένουν:", matches)
83 # εναλλαγή παίκτη
84 player = next(player)
ΤΟ ΠΑΙΧΝΙΔΙ ΤΗΣ ΑΦΑΙΡΕΣΗΣ 12

85 # εμφάνιση αποτελέσματος παιχνιδιού


86 if player == computer:
87 print("Κέρδισε ο υπολογιστής.")
88 else:
89 print("Παίκτη", player, "κέρδισες!")
nim/src/nim.final.py

Τροποποιήσεις – Επεκτάσεις
4.1 Το τελικό πρόγραμμα συμπεριφέρεται λίγο ανόητα όταν απομείνει μόνο
ένα σπίρτο: ενώ είναι βέβαιο ότι ο παίκτης που έχει σειρά να παίξει έχει
χάσει, τον ρωτάει κανονικότατα πόσα σπίρτα θέλει να αφαιρέσει. Μάλι-
στα, αν πληκτρολογήσει έναν μη-έγκυρο αριθμό, του απαντά με το μή-
νυμα Πάρε από 1 μέχρι και 1 σπίρτα .
Να τροποποιήσετε το πρόγραμμα έτσι ώστε το παιχνίδι να σταματά αυ-
τόματα όταν απομένει μόνο ένα σπίρτο, χωρίς να ρωτάει τον παίκτη πόσα
σπίρτα θέλει να αφαιρέσει.
Για να σταματάει το παιχνίδι όταν απομένει μόνο ένα σπίρτο, αρκεί μια μι-
κρή τροποποίηση στη συνθήκη της while. Όμως το σημείο που χρειάζεται
προσοχή είναι η ανακοίνωση του νικητή.
nim/exercises/subtraction-oneleft.py

4.2 Να υλοποιήσετε την συνάρτηση readMatches() χρησιμοποιώντας μόνο


την break για να διακόψετε τον επαναληπτικό έλεγχο. Η συνθήκη συ-
νέχειας της while θα πρέπει να είναι η True.
nim/exercises/subtraction-break.py

4.3 Να υλοποιήσετε μια συνάρτηση η οποία ανακοινώνει το νικητή του παι-


χνιδιού, αφού δεχτεί τις κατάλληλες παραμέτρους. Χρησιμοποιήστε την
συνάρτησή σας καλώντας την από το κύριο πρόγραμμα.
nim/exercises/subtraction-announce.py

4.4 Να τροποποιήσετε το παιχνίδι έτσι ώστε ο παίκτης που παίρνει τα τελευ-


ταία σπίρτα να κερδίζει. Ποιά είναι τώρα η βέλτιση στρατηγική για έναν
παίκτη; Τροποποιήστε ανάλογα την συνάρτηση computeMatches() που
υπολογίζει τον αριθμό σπίρτων που πρέπει ν’ αφαιρεθούν σε μια δεδομένη
κατάσταση.
nim/exercises/subtraction-nonmisere.py

4.5 Η συνάρτηση next δέχεται σαν παράμετρο τον αριθμό p ενός παίκτη και
επιστρέφει τον αριθμό του παίκτη που παίζει μετά από τον p. Θεωρείται
όμως δεδομένο ότι στο παιχνίδι συμμετέχουν δύο παίκτες.
Να επεκτείνετε τη συνάρτηση next, έτσι ώστε να δέχεται σαν επιπρό-
σθετη παράμετρο το πλήθος q των παικτών που συμμετέχουν στο παι-
χνίδι. Έτσι, η ίδια συνάρτηση θα μπορεί να χρησιμοποιηθεί σε διαφορε-
τικά παιχνίδια.
Η τιμή της παραμέτρου q μπορεί να είναι οποιοσδήποτε ακέραιος. Δε χρειά-
ζεται να υποθέσετε ότι υπάρχει κάποιο όριο.
nim/exercises/next-generalized.py
ΤΟ ΠΑΙΧΝΙΔΙ ΤΗΣ ΑΦΑΙΡΕΣΗΣ 13

Ασκήσεις
4.6 Στο παιχνίδι «Τα Ζυγά Κερδίζουν» οι δύο παίκτες ξεκινούν με μια σειρά
από αντικείμενα. Το αρχικό πλήθος των αντικειμένων πρέπει να είναι
περιττός αριθμός. Κάθε ένας από τους δύο παίκτες αφαιρεί με τη σειρά
του από ένα μέχρι και τέσσερα αντικείμενα, μέχρι ν’ αφαιρεθούν όλα.
Νικητής είναι ο παίκτης που στο τέλος του παιχνιδιού απομένει με άρτιο
(ζυγό) πλήθος αντικειμένων. Να αναπτύξετε ένα πρόγραμμα που θα δια-
βάζει σε κάθε γύρο τον αριθμό των σπίρτων που αφαιρεί ο παίκτης που
έχει σειρά και στο τέλος ανακοινώνει το νικητή.
nim/exercises/evenwins.py

4.7 Στο παιχνίδι «Τα Ζυγά Κερδίζουν» υπάρχουν επίσης ανεπιθύμητες νη-
σίδες, δηλαδή καταστάσεις από τις οποίες ένας παίκτης δεν μπορεί ν’
αποφύγει την ήττα, εφόσον ο αντίπαλός του παίξει σωστά. Αν ένας παί-
κτης δεν βρίσκεται σε ανεπιθύμητη νησίδα τότε μπορεί να οδηγήσει τον
αντίπαλό του σε μία και να κερδίσει. Προσπαθήστε να καταγράψετε τις
πιθανές καταστάσεις του παιχνιδιού όταν απομένουν λίγα σπίρτα (π.χ.
από 1 μέχρι και 6) και ποια είναι η καλύτερη κίνηση σε κάθε μία απ’
αυτές. Το εγχείρημα είναι δυσκολότερο απ’ ότι στο Παιχνίδι της Αφαί-
ρεσης γιατί εδώ η καλύτερη κίνηση δεν εξαρτάται μόνο από το πλήθος
των σπίρτων που απομένουν. Πρέπει να λάβετε υπόψη αν ο καθένας από
τους δύο παίκτες έχει συγκεντρώσει άρτιο ή περιττό πλήθος σπίρτων.
Τελικός σας σκοπός είναι να τροποποιήσετε το πρόγραμμα της προηγού-
μενης άσκησης έτσι ώστε να παίζει με αντίπαλο το χρήστη.
nim/exercises/evenwins-auto.py

4.8 Να επεκτείνετε το παιχνίδι Ανάμεσα ή Acey Ducey (κεφ. 2), έτσι ώστε
να παίζεται επαναληπτικά. Το πρόγραμμά σας θα πρέπει αρχικά να ρω-
τάει τον παίκτη το συνολικό ποσό με το οποίο επιθυμεί να ξεκινήσει το
παιχνίδι. Με το ίδιο ακριβώς ποσό θα ξεκινήσει και η «μάνα». Σε κάθε
γύρο, όταν ο παίκτης ερωτάται για το ποσό που θα στοιχηματίσει, το
πρόγραμμά σας θα πρέπει να ελέγχει ότι το ποσό αυτό είναι θετικό και
δεν ξεπερνά το συνολικό ποσό που διαθέτει εκείνη την στιγμή ο παί-
κτης, διαφορετικά θα πρέπει να επαναλαμβάνει την ερώτηση μέχρι το
στοίχημα να είναι έγκυρο. Το παιχνίδι θα τελειώνει όταν εξαντληθούν τα
χρήματα του παίκτη ή της «μάνας».
nim/exercises/aceyducey-iterative.py

4.9 Να μετατρέψετε το «Μάντεψε τον Αριθμό» σε παιχνίδι δύο παικτών, ανά-


μεσα στο χρήστη και το πρόγραμμά σας. Τόσο ο χρήστης, όσο και το πρό-
γραμμα, θα επιλέγουν στην αρχή του παιχνιδιού έναν μυστικό αριθμό και
θα προσπαθούν να μαντέψουν ο ένας τον αριθμό του άλλου, διαθέτοντας
ένα συγκεκριμένο πλήθος προσπαθειών. Σε κάθε γύρο του παιχνιδιού,
κάθε ένας από τους δύο παίκτες θα επιλέγει έναν αριθμό και ο άλλος
παίκτης θα τον ενημερώνει αν ο μυστικός αριθμός του είναι ίσος, μικρό-
τερος ή μεγαλύτερος από αυτόν.
Στο παράδειγμα που ακολουθεί δίνεται η αλληλεπίδραση μεταξύ χρή-
στη και προγράμματος για έναν γύρο του παιχνιδιού. Πρώτα επιλέγει
αριθμό ο παίκτης, προσπαθώντας να μαντέψει τον μυστικό αριθμό του
ΤΟ ΠΑΙΧΝΙΔΙ ΤΗΣ ΑΦΑΙΡΕΣΗΣ 14

προγράμματος, και στη συνέχεια το ίδιο το πρόγραμμα, προσπαθώντας


να μαντέψει τον μυστικό αριθμό του παίκτη.
Σου απομένουν 3 προσπάθειες.
Μάντεψε τον αριθμό μου: 25
Λάθος. Είναι μεγαλύτερος.

Μου απομένουν 3 προσπάθειες.


Επιλέγω τον αριθμό 24. Ο αριθμός σου είναι:
1. Μικρότερος 2. Μεγαλύτερος 3. Ίσος; 1
Το ποιος από τους δύο παίκτες θα ξεκινήσει πρώτος θα επιλέγεται τυχαία.
Αν ο παίκτης που παίξει πρώτος βρει τον μυστικό αριθμό, το πρόγραμμα
δεν θα πρέπει να διακόπτεται άμεσα, αλλά να ολοκληρώνει το γύρο, δί-
νοντας ουσιαστικά και στο δεύτερο παίκτη το ίδιο πλήθος προσπαθειών.
nim/exercises/guess-twoplayer.py

4.10 Τα παιδιά στο δημοτικό μαθαίνουν πρόσθεση μέσα από ασκήσεις αυξανό-
μενης δυσκολίας. Αρχικά, τους ζητείται να προσθέσουν δύο μονοψήφιους
αριθμούς, το άθροισμα των οποίων δεν ξεπερνά το 9.
Παράδειγμα: 4 + 2 = _
Μετά τους ζητείται να προσθέσουν δύο μονοψήφιους αριθμούς, το άθροι-
σμα των οποίων ξεπερνά το 10.
Παράδειγμα: 4 + 8 = _
Το επόμενο στάδιο είναι η πρόσθεση ενός διψήφιου κι ενός μονοψήφιου,
το άθροισμα των οποίων πιθανώς να ξεπερνά τη δεκάδα του διψήφιου,
αλλά όχι το 99.
Παράδειγμα: 42 + 9 = _
Μπορείτε να φτιάξετε ένα πρόγραμμα εξάσκησης για τα παιδιά του Δη-
μοτικού. Αρχικά, το πρόγραμμά σας θα πρέπει να ρωτά ποιο είναι το επι-
θυμητό επίπεδο ασκήσεων και να παράγει πέντε από αυτές. Εδώ έχουμε
περιγράψει τα τρία πρώτα επίπεδα, αλλά εσείς μπορείτε να φτιάξετε κι
άλλα ή ακόμα και να περάσετε σε άλλες πράξεις εκτός από την πρό-
σθεση. Μην σας απασχολεί αν καμιά φορά τυχαίνει να εμφανίζονται δι-
πλότυπες ασκήσεις, αυτό προς το παρόν είναι δύσκολο να το αποφύγετε.
Αν το παιδί κάνει λάθος σε κάποια από τις ερωτήσεις το πρόγραμμα μπο-
ρεί να του εμφανίζει άμεσα την σωστή απάντηση ή να του δίνει κι άλλες
ευκαιρίες. Ακόμα καλύτερο θα ήταν αν το πρόγραμμά σας εξηγούσε πως
προκύπτει η σωστή απάντηση ή έδινε μια υπόδειξη πριν το παιδί ξανα-
προσπαθήσει.
Παράδειγμα: 42 + 9 = _. Το παιδί πληκτρολογεί 52 και το πρόγραμμα του
ζητά να ξαναπροσπαθήσει, υποδεικνύοντας ότι 42 + 9 = 42 + 8 + 1 = _
nim/exercises/mathgame.py
ΤΟ ΠΑΙΧΝΙΔΙ ΤΗΣ ΑΦΑΙΡΕΣΗΣ 15

Υποπρογραμματα Είδαμε ήδη ότι για να αντιμετωπίσουμε την αυξανόμενη έκταση και πολυπλοκότητα των
προγραμμάτων χρησιμοποιούμε υποπρογράμματα, μικρά προγράμματα που τα συνδυάζουμε για να φτιάξουμε
συνθετότερα, κατά τον ίδιο τρόπο με τον οποίο κατασκευάζουμε μια περίπλοκη μηχανή από απλούστερα εξαρτή-
ματα, κάθε ένα από τα οποία εξυπηρετεί μια συγκεκριμένη λειτουργία. Ο κατακερματισμός των προγραμμάτων
σε απλούστερα μας βοηθά να σκεφτόμαστε τμηματικά και να αναλύουμε το γενικότερο πρόβλημα σε επιμέρους
απλούστερα προβλήματα. Τα υποπρογράμματα θα πρέπει να θεωρούνται όσο το δυνατόν ανεξάρτητα από το
πλαίσιο μέσα στο οποίο χρησιμοποιούνται. Αντιμετωπίζουμε κάθε υποπρόγραμμα ως ένα κομμάτι κώδικα που
λύνει ένα μικρό πρόβλημα με γενικό τρόπο και έτσι είναι επαναχρησιμοποιήσιμο. Μπορεί να κληθεί από δια-
φορετικά σημεία και για διαφορετικούς λόγους μέσα σ’ ένα πρόγραμμα, αλλά και να χρησιμοποιηθεί και σε άλλα
προγράμματα στο μέλλον. Οι εσωτερικές λεπτομέρειες της λειτουργίας κάθε υποπρογράμματος είναι κρυμμέ-
νες για εκείνους που το χρησιμοποιούν. Όποιος θέλει να χρησιμοποιήσει ένα υποπρόγραμμα δεν χρειάζεται να
καταλαβαίνει πως λειτουργεί και με ποια μέθοδο υπολογίζεται το αποτέλεσμα. Ο Al Sweigart, στο εξαιρετικό
του βιβλίο Invent Your Own Computer Games with Python, γράφει: «Το ωραίο με τις συναρτήσεις είναι ότι
χρειάζεται μόνο να ξέρουμε τι κάνουν, αλλά όχι πως το κάνουν.» Αντίστοιχα κρυμμένες είναι και τυχόν αλλαγές
στα υποπρογράμματά μας. Αν το υποπρόγραμμα τροποποιηθεί (επειδή βρήκαμε κάποιο λάθος, κάποιο τρόπο
να λειτουργεί καλύτερα ή ακόμα και κάποια καλύτερη μέθοδο) οι αλλαγές θ’ αφορούν μόνο το εσωτερικό του
υποπρογράμματος, ενώ το υπόλοιπο πρόγραμμα θα παραμείνει αμετάβλητο.

Υπολογιστες και Παιχνιδια Στις αρχές της δεκαετίας του 1950 άρχισε να εξετάζεται συστηματικά η δυνατό-
τητα χρήσης των ηλεκτρονικών υπολογιστών, που ήταν ακόμα στα πρώτα τους βήματα, για το παίξιμο παιχνι-
διών. Ο Claude Shannon δημοσίευσε το 1950 μια εργασία με τίτλο Programming a Computer for Playing Chess.
Εκεί περιέγραψε ένα σύνολο τεχνικών που θα επέτρεπαν σ’ έναν υπολογιστή να απαριθμεί συστηματικά τις πι-
θανές κινήσεις ενός παιχνιδιού, να τις αξιολογεί και να παίρνει αποφάσεις σχετικά με τις κινήσεις που θα έπρεπε
να επιλέξει. Μια παρόμοια εργασία, με τίτλο Digital Computers Applied to Games, δημοσίευσε το 1953 και ο
Alan Turing. Το 1997, ένας υπολογιστής της IBM με το όνομα Deep Blue, κέρδισε τον παγκόσμιο πρωταθλητή
Gary Kasparov. Σήμερα, ένας οικιακός υπολογιστής έχει την υπολογιστική ισχύ του Deep Blue και λέγεται ότι
ο τρόπος παιχνιδιού των πρωταθλητών έχει αλλάξει σημαντικά από τότε που ισχυρά σκακιστικά προγράμματα
χρησιμοποιούνται για προπόνηση και ανάλυση θέσεων. Αν και το σκάκι θεωρείται ο βασιλιάς των παιχνιδιών
και συνδέεται παραδοσιακά με τη νοημοσύνη, δεν είναι φυσικά το μόνο παιχνίδι που έχει απασχολήσει τους
ερευνητές. Ένα ιστορικό πρόγραμμα που έπαιζε ντάμα αναπτύχθηκε από τον Arthur Samuel τη δεκαετία του
1950. To 1989 ξεκίνησε η ανάπτυξη του προγράμματος Chinook, το οποίο έπαιξε με τον παγκόσμιο πρωταθλητή
το 1992. Το 2007, μετά από σχεδόν 20 χρόνια υπολογισμών, έγινε ανίκητο “λύνοντας” το παιχνίδι της ντάμας,
έχοντας απαριθμήσει όλες τις πιθανές θέσεις του παιχνιδιού (περίπου 500 δισεκατομμύρια δισεκατομμύρια) και
γνωρίζοντας τη βέλτιστη κίνηση για κάθε μια από αυτές. Το ΝΙΜ είναι πολύ απλούστερο παιχνίδι. Οι (απο-
δεδειγμένα) καλύτερες δυνατές κινήσεις είναι γνωστές και υπολογίζονται εύκολα. Αυτός είναι ο λόγος που το
ΝΙΜ ήταν πιθανότατα το πρώτο παιχνίδι που αυτοματοποιήθηκε.

Υπολογιστες και Νοημοσυνη Είναι αναπόφευκτο να αναρωτηθεί κανείς πόσο “έξυπνο” είναι ένα σκακιστικό
πρόγραμμα που κερδίζει παγκόσμιους πρωταθλητές. Το ερώτημα αν οι υπολογιστές έχουν τη δυνατότητα να
επιδεικνύουν “νοημοσύνη” προέκυψε πολύ νωρίς και είναι τόσο δύσκολο ν’ απαντηθεί όσο να ορίσει κανείς τι
είναι η ευφυία και η νοημοσύνη. Η Ada Lovelace, κόρη του λόρδου Βύρωνα και στενή συνεργάτης του Charles
Babbage είχε γράψει το 1842 ότι οι υπολογιστικές μηχανές μπορούν να κάνουν μόνο αυτά που εμείς μπορούμε
να τις προγραμματίσουμε να κάνουν. Ο χαρισματικός Alan Turing έγραψε το 1950 ένα ιστορικό άρθρο με τίτλο
Computing Machinery and Intelligence (Υπολογιστικές Μηχανές και Νοημοσύνη), το οποίο ξεκινούσε με την
ΤΟ ΠΑΙΧΝΙΔΙ ΤΗΣ ΑΦΑΙΡΕΣΗΣ 16

πρόταση “Προτείνω να εξετάσουμε την ερώτηση: Μπορούν οι μηχανές να σκεφτούν;” Πολύ συνοπτικά, ο Turing
ισχυρίστηκε ότι η απάντηση μπορεί να είναι καταφατική όταν μια μηχανή μπορεί να συνομιλήσει μ’ έναν άν-
θρωπο και να τον πείσει ότι δεν είναι μηχανή. Είναι σημαντικό ότι ο Turing εστίασε στον τρόπο με τον οποίο
οι εξωτερικοί παρατηρητές αντιλαμβάνονται την συμπεριφορά μιας μηχανής ως εφυή, χωρίς να έχει σημασία
με ποιες εσωτερικές διεργασίες επιτυγχάνεται αυτό. Οι απόψεις του Turing δεν είναι καθολικά αποδεκτές, το
αντίθετο μάλιστα. Είναι πάντως σημαντικό να θυμάστε ότι οι υπολογιστές είναι σχεδιασμένοι για να κάνουν
μόνο ένα πράγμα: να εκτελούν προγράμματα, τα οποία επεξεργάζονται τις τιμές που δέχονται στην είσοδό
τους και επιστρέφουν αποτελέσματα στην έξοδό τους. Με την πάροδο του χρόνου, τα εξαρτήματά των υπολο-
γιστών εξελίσσονται, οι ταχύτητες με τις οποίες λειτουργούν αυξάνονται και οι συσκευές εισόδου-εξόδου που
διαθέτουν επιτρέπουν πλέον μια πολύ φυσική αλληλεπίδραση με τους χρήστες. Όμως το γεγονός παραμένει πως
για κάθε τι που κάνουν, υπάρχει ένα πρόγραμμα, γραμμένο από ανθρώπους, που τους υπαγορεύει με απόλυτη
ακρίβεια πως να το κάνουν. Υπάρχουν βέβαια και προγράμματα που μαθαίνουν και προσαρμόζουν την συμπε-
ριφορά τους. Υπό αυτή την έννοια, η συμπεριφορά τους δεν είναι προγραμματισμένη, αλλά ο μηχανισμός μέσω
του οποίου την τροποποιούν καθώς μαθαίνουν είναι. Έτσι, τα αυτοκίνητα της Google κινούνται χωρίς οδηγό
για χιλιάδες μίλια χωρίς ατυχήματα, το ρομπότ της NASA Curiosity πλοηγείται αυτόνομα στην επιφάνεια του
Άρη, το σύστημα Watson της IBM κερδίζει σε τηλεπαιχνίδια και βοηθά στη διάγνωση ασθενειών, επειδή ομάδες
ανθρώπων καθόρισαν (με πολύ κόπο) πως ακριβώς μπορούν να γίνουν όλα αυτά.
Η Μέρα της Εβδομάδας 4
Στο κεφάλαιο αυτό θ’ αναπτύξουμε ένα πρόγραμμα για να υπολογίζουμε
σε ποια ημέρα της εβδομάδας αντιστοιχεί μια συγκεκριμένη ημερομηνία. 29 Οκτωβρίου 2015
Θα δανειστούμε τη μέθοδο υπολογισμού από τον Gauss. Στην πορεία θα 22:07

έχουμε την ευκαιρία να εξασκηθούμε σε όλα τα προγραμματιστικά εργα-


λεία που έχουμε συναντήσει μέχρι τώρα.
Έννοιες: δομή επιλογής, δομή επανάληψης, υποπρογράμματα, πλειάδες

Η επίλυση ημερολογιακών και αστρονομικών προβλημάτων ήταν


ένα από τα πρώτα πεδία εφαρμογής υπολογιστικών μεθόδων. Η μέ-
τρηση του χρόνου και η πρόβλεψη των κινήσεων των ουράνιων σω-
μάτων ήταν προβλήματα εξαιρετικής σημασίας καθ’ όλη την ιστο-
ρία της ανθρωπότητας, με πρακτικές εφαρμογές αλλά κι επιπτώ-
σεις πολιτικές, επιστημονικές και φιλοσοφικές. Η ύπαρξη του μη-
χανισμού των Αντικυθήρων δείχνει από πόσο νωρίς επιδιώχθηκε η
αυτοματοποίηση τέτοιου είδους υπολογισμών.
Το να υπολογίσει κανείς την ημέρα της εβδομάδας για μια συγκε-
κριμένη ημερομηνία είναι ένα καλά μελετημένο πρόβλημα. Για την
επίλυση του προβλήματος υπάρχουν πολλές προσεγγίσεις. Μερι-
κές βασίζονται σε μνημονικούς κανόνες, με αποτέλεσμα ορισμένοι
άνθρωποι να είναι σε θέση να λύσουν το πρόβλημα μέσα σε μερικά
δευτερόλεπτα, κάνοντας υπολογισμούς μόνο με το μυαλό τους. Εμείς Carl Friedrich Gauss (1777-1855)
θα χρησιμοποιήσουμε μια μέθοδο του Gauss, η οποία είναι απλού- Ο ιδιοφυής μαθηματικός που ασχολή-
στατη στην υλοποίησή της, αν και ο τρόπος με τον οποίο λειτουργεί θηκε με αναρίθμητα προβλήματα σε
δεν είναι προφανής. πολλά διαφορετικά πεδία.

Πες Μου Πότε


Ας ξεκινήσουμε ρωτώντας το χρήστη ποια είναι η ημερομηνία για
την οποία θα ήθελε να μάθει την αντίστοιχη ημέρα της εβδομάδας.
1 # είσοδος ημερομηνίας από το χρήστη
2 print("Έτος: ", end="")
3 year = int(input())
4 print("Mήνας: ", end="")
5 month = int(input())
6 print("Ημέρα: ", end="")
7 day = int(input())

1
Η ΜΕΡΑ ΤΗΣ ΕΒΔΟΜΑΔΑΣ 2

Εναλλακτικά:
# είσοδος ημερομηνίας από το χρήστη
year = int(input("Έτος: "))
month = int(input("Mήνας: "))
day = int(input("Ημέρα: "))

Τί Μέρα Έχουμε Σήμερα;


Και τώρα που έχουμε την ημερομηνία πώς θα υπολογίσουμε το ζητούμενο;
Ποιά είναι η μέθοδος του Gauss;
Οι γραμμές που ακολουθούν είναι μια υλοποίηση της μεθόδου του
Gauss. Πρόκειται για εντολές που υπολογίζουν την τιμή ορισμένων
αριθμητικών παραστάσεων, για να μας δώσουν τελικά το αποτέλε-
σμα που χρειαζόμαστε: έναν ακέραιο από το 0 μέχρι και το 6, που
αντιστοιχεί στις ημέρες από την Κυριακή μέχρι και το Σάββατο. Ο
τρόπος που λειτουργεί η μέθοδος δεν είναι προφανής, αλλά εδώ μας
ενδιαφέρει να τη χρησιμοποιήσουμε, όχι να την αναλύσουμε.
8 # υπολογισμός ημέρας με την μέθοδο του Gauss
9 # προσαρμογή μήνα και έτους 2 μήνες προς τα πίσω
10 if month > 2:
11 month = month - 2
12 else:
13 month = month + 10
14 year = year - 1
15 # όρος για δίσεκτα έτη
16 leap = 5*(year%4) + 4*(year%100) + 6*(year%400)
17 # υπολογισμός ημέρας της εβδομάδας
18 weekday = (day + leap + int(2.6 * month - 0.2)) % 7
Μπορείτε να ελέγξετε αν οι εντολές λειτουργούν σωστά κάνοντας
μια δοκιμή με την σημερινή ημερομηνία. Δεν θα πρέπει βέβαια να
παραλείψουμε την εμφάνιση του αποτελέσματος στην οθόνη.
19 # εμφάνιση ημέρας της εβδομάδας (0:Κυριακή - 6:Σάββατο)
20 print("Ημέρα της εβδομάδας:", weekday)
src/weekday.1.py

Κλεισμένη στο Κουτάκι


Αυτή η μέθοδος είναι εντυπωσιακή: υπολογίζει το αποτέλεσμα με λίγες
πράξεις, αν και το πρόβλημα είναι περίπλοκο. Ωστόσο με ενοχλεί να
βλέπω μέσα στο πρόγραμμα αυτόν τον ακατάληπτο κώδικα.
Μπορούμε να απομονώσουμε τον ομολογουμένως δυσνόητο υπολο-
γισμό της ημέρας της εβδομάδας μέσα σ’ ένα υποπρόγραμμα. Αυτό
θα δέχεται μια τριάδα παραμέτρων (ημέρα, μήνας, έτος) που αντι-
στοιχούν σε μια ημερομηνία και θα επιστρέφει έναν ακέραιο από το
0 μέχρι και το 6, που αντιστοιχεί σε μια ημέρα της εβδομάδας.
Η ΜΕΡΑ ΤΗΣ ΕΒΔΟΜΑΔΑΣ 3

1 def computeWeekday(day, month, year):


2 """ Υπολογίζει με την μέθοδο του Gauss
3 την ημέρα της εβδομάδας στην οποία αντιστοιχεί
4 μια συγκεκριμένη ημερομηνία.
5 day, month, year: ορίζει την ημερομηνία
6 """
7 # προσαρμογή μήνα και έτους 2 μήνες προς τα πίσω
8 if month > 2:
9 month = month - 2
10 else:
11 month = month + 10
12 year = year - 1
13 # όρος για δίσεκτα έτη
14 leap = 5*(year%4) + 4*(year%100) + 6*(year%400)
15 # υπολογισμός κι επιστροφή ημέρας της εβδομάδας
16 return (day + leap + int(2.6 * month - 0.2)) % 7
Ο κώδικας της μεθόδου δεν έχει αλλάξει, αλλά πλέον είναι «κλεισμέ-
νος» μέσα στο υποπρόγραμμα. Όποιος θέλει να το χρησιμοποιήσει
δεν χρειάζεται να καταλαβαίνει πως λειτουργεί. Δεν χρειάζεται καν
να γνωρίζει με ποια μέθοδο υπολογίζεται το αποτέλεσμα.
Στο κύριο πρόγραμμα, απλά καλούμε την συνάρτηση, δίνοντας ως
παραμέτρους τις τιμές που διαβάσαμε από το χρήστη.
17 # είσοδος ημερομηνίας από το χρήστη
18 print("Έτος: ", end="")
19 year = int(input())
20 print("Mήνας: ", end="")
21 month = int(input())
22 print("Ημέρα: ", end="")
23 day = int(input())
24 # κλήση υποπρογράμματος για υπολογισμό ημέρας
25 weekday = computeWeekday(day, month, year)
src/weekday.2.py

Στις 32 Του Μηνός


Ο χρήστης μπορεί να δώσει αυθαίρετους αριθμούς για το έτος, τον μήνα
και την ημέρα. Δεν χρειάζεται κάποιος έλεγχος;
Είναι απαραίτητο η τιμή που θα δοθεί για το μήνα να είναι ανάμεσα
στο 1 και το 12. Επίσης, είναι απαραίτητο η τιμή που θα δοθεί για
την ημέρα να είναι ανάμεσα στο 1 και το πλήθος των ημερών του
μήνα. Για το έτος υπάρχει περιθώριο ελαστικότητας, αλλά και πάλι
θα ήταν καλό να περιορίσουμε την τιμή σε λογικά όρια.
Ας εξετάσουμε αρχικά πως θα μπορούσαμε να ελέγξουμε ότι η τιμή
που δίνεται για τον μήνα είναι έγκυρη. Πιθανώς το πρώτο πράγμα
που θα έγραφε κανείς να ήταν το εξής:
Η ΜΕΡΑ ΤΗΣ ΕΒΔΟΜΑΔΑΣ 4

print("Mήνας: ", end="")


month = int(input())
if month < 1 or month > 12:
# μήνυμα λάθους
print("Δώστε τιμή από 1 μέχρι 12")
Όμως αυτό δεν αρκεί γιατί, σε περίπτωση μη-έγκυρης τιμής, ο χρή-
στης θα πρέπει και πάλι να πληκτρολογήσει μια τιμή για τον μήνα, η
οποία θα πρέπει και πάλι να ελεγχθεί και, πιθανώς, να μην είναι ούτε
αυτή την φορά έγκυρη, κ.ο.κ. Με άλλα λόγια, η ανάγνωση τιμής και
ο έλεγχός της θα πρέπει να επαναλαμβάνονται, μέχρι να διαβαστεί
έγκυρη τιμή. Θα χρειαστούμε λοιπόν μια δομή επανάληψης.
# επανάληψη, όσο η τιμή είναι εκτός ορίων
while True:
print("Mήνας: ", end="")
month = int(input())
if month < 1 or month > 12:
# μήνυμα λάθους
print("Δώστε τιμή από 1 μέχρι 12")
else:
break
Εδώ ο χρήστης πρακτικά εγκλωβίζεται σ’ έναν κύκλο. Η ανάγνωση
τιμής (μαζί με το σχετικό μήνυμα λάθους) επαναλαμβάνεται μέχρι ο
χρήστης να παρέχει μια αποδεκτή τιμή. Μια εναλλακτική προσέγ-
γιση της ίδιας ιδέας είναι η εξής:
20 print("Mήνας: ", end="")
21 month = int(input())
22 # επανάληψη, όσο η τιμή είναι εκτός ορίων
23 while month < 1 or month > 12:
24 # μήνυμα λάθους
25 print("Δώστε τιμή από 1 μέχρι 12")
26 print("Mήνας: ", end="")
27 month = int(input())
src/weekday.3.py

Και σε πολλά άλλα προγράμματα θα χρειαστεί να διαβάσουμε από


το χρήστη τιμές, εξασφαλίζοντας ότι οι τιμές αυτές είναι έγκυρες,
δηλαδή ικανοποιούν κάποιους περιορισμούς.

Μια Απ’ Τα Ίδια;


Μου φαίνεται πως αυτό ακριβώς που κάναμε για το μήνα θα πρέπει να
το κάνουμε για το έτος και για την ημέρα (αλλά με διαφορετικά όρια).
Πάλι τα ίδια θα γράφουμε;
Και για τις τρεις τιμές (έτος, μήνας, ημέρα) έχουμε το ίδιο ακριβώς
πρόβλημα: θέλουμε να εμφανίζεται στο χρήστη μια προτροπή και
στη συνέχεια να διαβάζεται από το χρήστη μια ακέραια τιμή που
θα πρέπει να βρίσκεται εντός συγκεκριμένων ορίων.
Η ΜΕΡΑ ΤΗΣ ΕΒΔΟΜΑΔΑΣ 5

Θα λύσουμε αυτό το πρόβλημα με γενικό τρόπο, κατασκευάζοντας


ένα υποπρόγραμμα που δέχεται σαν παραμέτρους την προτροπή και
τα όρια κι επιστρέφει την τιμή που διαβάστηκε από το χρήστη. Ου-
σιαστικά θα γενικεύσουμε τον κώδικα που αναπτύξαμε λίγο πριν για
τον έλεγχο της τιμής του μήνα.
17 def readInt(prompt,lower,upper):
18 """ Εμφανίζει μια προτροπή και μετά διαβάζει
19 από το χρήστη κι επιστρέφει έναν ακέραιο αριθμό,
20 εξασφαλίζοντας ότι βρίσκεται εντός ορίων.
21 prompt: η προτροπή που εμφανίζεται στο χρήστη
22 lower, upper: τα όρια
23 """
24 # εμφάνιση προτροπής και ανάγνωση τιμής
25 print(prompt, end="")
26 num = int(input())
27 # επανάληψη, όσο η τιμή είναι εκτός ορίων
28 while num < lower or num > upper:
29 # μήνυμα λάθους
30 print("Δώστε τιμή από",lower,"μέχρι",upper)
31 # εμφάνιση προτροπής και ανάγνωση τιμής
32 print(prompt, end="")
33 num = int(input())
34 # η έγκυρη τιμή επιστρέφεται
35 return num
Θα χρησιμοποιήσουμε αυτό το υποπρόγραμμα τρεις φορές, καλώ-
ντας το κάθε φορά με διαφορετικές παραμέτρους, για να εισάγουμε
τιμές για το έτος, το μήνα και την ημέρα.
36 # είσοδος ημερομηνίας από το χρήστη
37 year = readInt("Έτος: ", 1923, 2999)
38 month = readInt("Mήνας: ", 1, 12)
39 day = readInt("Ημέρα: ", 1, 31)
40 # κλήση υποπρογράμματος για υπολογισμό ημέρας
41 weekday = computeWeekday(day, month, year)
src/weekday.4.py

Το 1923 είναι η χρονιά που υιοθετήθηκε η χρήση του Γρηγοριανού


ημερολογίου στην Ελλάδα. Το άνω όριο είναι αυθαίρετο.
Αναπτύξαμε αυτό το υποπρόγραμμα γιατί ήταν απαραίτητο σε τρία
διαφορετικά σημεία του προγράμματός μας να διαβάζουμε έναν ακέ-
ραιο αριθμό εντός συγκεκριμένων ορίων. Αντί να αντιγράψουμε τις
ίδιες εντολές σε τρία σημεία, τις τοποθετήσαμε μέσα στο υποπρό-
γραμμα για να τις καλούμε όποτε τις χρειαζόμαστε.
Όμως το υποπρόγραμμα που αναπτύξαμε δεν λειτουργεί μόνο για
έτη, μήνες και ημέρες, αλλά μπορεί να χρησιμοποιηθεί και σε οποια-
δήποτε άλλη περίπτωση θέλουμε να διαβάσουμε από το χρήστη έναν
ακέραιο αριθμό εντός ορίων. Με άλλα λόγια, το υποπρόγραμμα λύ-
νει το πρόβλημα με γενικό τρόπο και είναι επαναχρησιμοποιήσιμο.
Η ΜΕΡΑ ΤΗΣ ΕΒΔΟΜΑΔΑΣ 6

Πόσες Έχει Ο Μήνας;


Δεν έχουν όμως όλοι οι μήνες 31 ημέρες. Αφού τον κάνουμε τον έλεγχο,
ας τον κάνουμε σωστά.
Στο σημείο αυτό χρειάζεται να υπολογίσουμε το πλήθος των ημερών
ενός μήνα. Όπως φαντάζεστε, θα λύσουμε αυτό το υποπρόβλημα
κατασκευάζοντας το αντίστοιχο υποπρόγραμμα, ένα ακόμα “εξάρ-
τημα” που κάνει μια συγκεκριμένη δουλειά: δέχεται σαν παράμετρο
το μήνα και το έτος κι επιστρέφει τον αριθμό των ημερών του μήνα.
Οι κανόνες που καθορίζουν πόσες ημέρες έχει ο κάθε μήνας είναι
οι εξής: πριν τον Αύγουστο, οι άρτιοι μήνες έχουν 30 ημέρες και οι
περιττοί έχουν 31, ενώ από τον Αύγουστο και μετά ισχύει το αντί-
στροφο. Εξαίρεση αποτελεί ο Φλεβάρης που έχει 28 ημέρες, εκτός κι
αν το έτος είναι δίσεκτο, οπότε έχει 29.
Από τα παραπάνω φαίνεται πως τίθεται ένα ακόμα μικρότερο πρό-
βλημα: χρειάζεται να γνωρίζουμε αν ένα έτος είναι δίσεκτο ή όχι.
Ας ξεκινήσουμε λοιπόν με την επίλυση αυτού του υποπροβλήματος,
κατασκευάζοντας το αντίστοιχο υποπρόγραμμα, το οποίο θα δέχε-
ται σαν παράμετρο ένα έτος και θα αποφαίνεται αν είναι δίσεκτο ή
όχι. Εξ’ ορισμού, δίσεκτα είναι τα έτη που διαιρούνται με το 4, αλλά
όχι με το 100, καθώς και τα έτη που διαιρούνται με το 400.
def isLeap(year):
""" Επιστρέφει την τιμή True αν το έτος year
είναι δίσεκτο, αλλιώς επιστρέφει False.
"""
if (year % 4 == 0 and year % 100 > 0) or
year % 400 == 0:
return True
else:
return False
Στην πραγματικότητα, η δομή επιλογής δεν είναι απαραίτητη. Ορ-
θότερη προσέγγιση είναι η εξής:
Η τιμή μιας συνθήκης είναι είτε αλη-
36 def isLeap(year):
θής (True), είτε ψευδής (False). Εδώ
37 """ Επιστρέφει την τιμή True αν το έτος year λοιπόν επιστρέφεται απευθείας η τιμή
38 είναι δίσεκτο, αλλιώς επιστρέφει False. της συνθήκης που ελέγχει αν το έτος
39 """ year είναι δίσεκτο.
40 return ((year % 4 == 0 and year % 100 > 0) or
41 year % 400 == 0)
Τώρα μπορούμε να υλοποιήσουμε το υποπρόγραμμα που υπολογί-
ζει τον αριθμό των ημερών ενός μήνα. Το έτος είναι απαραίτητο σαν
παράμετρος στο υποπρόγραμμα γιατί ο μήνας μπορεί να είναι ο Φλε-
βάρης, οπότε το έτος επηρρεάζει τον αριθμό των ημερών.
Η ΜΕΡΑ ΤΗΣ ΕΒΔΟΜΑΔΑΣ 7

42 def daysOfMonth(m,y):
43 """ Επιστρέφει το πλήθος των ημερών ενός μήνα.
44 m: ο αριθμός του μήνα (1-12)
45 y: ο αριθμός του έτους (απαραίτητο όταν m=2)
46 """
47 if m == 2:
48 # Φεβρουάριος
49 if isLeap(y):
50 return 29
51 else:
52 return 28
53 elif m <= 7:
54 # Ιανουάριος - Ιούλιος (πλην Φεβρουαρίου)
55 if m % 2 == 1:
56 return 31
57 else:
58 return 30
59 else:
60 # Αύγουστος - Δεκέμβριος
61 if m % 2 == 1:
62 return 30
63 else:
64 return 31
H ανάγνωση τιμής για την ημέρα μπορεί πλέον να γίνει καθορίζο-
ντας σωστά το πλήθος των ημερών του μήνα.
65 # είσοδος ημερομηνίας από το χρήστη
66 year = readInt("Έτος: ", 1923, 2999)
67 month = readInt("Mήνας: ", 1, 12)
68 day = readInt("Ημέρα: ", 1, daysOfMonth(month,year))
src/weekday.5.py

Από τα παραπάνω βγαίνει στην επιφάνεια το μεγαλύτερο πλεονέ-


κτημα που προκύπτει από τη χρήση υποπρογραμμάτων. Είμαστε
“αναγκασμένοι” να αναλύουμε τα προβλήματα και να τ’ αντιμετωπί-
ζουμε τμηματικά. Κάθε φορά εστιάζουμε την προσοχή μας σε μικρό-
τερα κι απλούστερα κομμάτια του γενικού προβλήματος. Στη συνέ-
χεια, συνθέτουμε τη λύση, συναρμολογώντας τα κομμάτια αυτά.

Και Κάτι Ακόμα


Δεν μου αρέσει που το πρόγραμμα εμφανίζει έναν αριθμό και πρέπει ο
χρήστης να σκέφτεται την αντιστοιχία με τις ημέρες.
Υπάρχουν αρκετοί τρόποι ν’ αντιστοιχίσουμε τους ακέραιους από
το 0 μέχρι και το 6 με τις ημέρες από την Κυριακή μέχρι και το Σάβ-
βατο. Ένας από αυτούς είναι να τοποθετήσουμε τα ονόματα των
ημερών της εβδομάδας μέσα σε μια πλειάδα. Η πλειάδα είναι μια
λίστα τιμών που δεν μπορεί να τροποποιηθεί.
Η ΜΕΡΑ ΤΗΣ ΕΒΔΟΜΑΔΑΣ 8

# πλειάδα με τα ονόματα των ημερών Το πρώτο στοιχείο της πλειάδας εί-


ναι το dayNames[0], με τιμή 'Κυρ',
dayNames = ('Κυρ','Δευ','Τρι','Τετ','Πεμ','Παρ','Σαβ')
το δεύτερο είναι το dayNames[1], με
Είναι η πρώτη φορά που ένα όνομα, το dayNames, δεν αντιστοιχεί τιμή 'Δευ', ενώ το τελευταίο είναι το
dayNames[6], με τιμή 'Σαβ'. Η αρίθ-
σε μια μεμονωμένη τιμή αλλά σε μια συλλογή τιμών, όπως είναι εδώ
μηση μπορεί να είναι κι αντίστροφη,
η πλειάδα με τα ονόματα των ημερών. με το πρώτο στοιχείο από το τέλος να
είναι το dayNames[-1], με τιμή 'Σαβ',
Η σειρά των ημερών μέσα στην πλειάδα έχει σημασία γιατί κάθε
κ.ο.κ.
θέση είναι αριθμημένη, με την αρίθμηση να ξεκινάει από το μηδέν.
Μπορούμε λοιπόν να χρησιμοποιήσουμε την (ακέραια) ημέρα της
εβδομάδας που έχουμε υπολογίσει για να έχουμε πρόσβαση στην
αντίστοιχη θέση της πλειάδας που περιέχει το όνομα της ημέρας.
69 # πλειάδα με τα ονόματα των ημερών
70 dayNames = ('Κυρ','Δευ','Τρι','Τετ','Πεμ','Παρ','Σαβ')
71 # κλήση υποπρογράμματος για υπολογισμό ημέρας
72 weekday = computeWeekday(day, month, year)
73 # εμφάνιση ημέρας της εβδομάδας (0:Κυριακή - 6:Σάββατο)
74 print("Ημέρα της εβδομάδας:", dayNames[weekday])
src/weekday.6.py

Πλήρες Τελικό Πρόγραμμα


1 def computeWeekday(day, month, year):
2 """ Υπολογίζει με την μέθοδο του Gauss
3 την ημέρα της εβδομάδας στην οποία αντιστοιχεί
4 μια συγκεκριμένη ημερομηνία.
5 day, month, year: ορίζει την ημερομηνία
6 """
7 # προσαρμογή μήνα και έτους 2 μήνες προς τα πίσω
8 if month > 2:
9 month = month - 2
10 else:
11 month = month + 10
12 year = year - 1
13 # όρος για δίσεκτα έτη
14 leap = 5*(year%4) + 4*(year%100) + 6*(year%400)
15 # υπολογισμός κι επιστροφή ημέρας της εβδομάδας
16 return (day + leap + int(2.6 * month - 0.2)) % 7
…συνεχίζεται στην επόμενη σελίδα.
Η ΜΕΡΑ ΤΗΣ ΕΒΔΟΜΑΔΑΣ 9

17 def readInt(prompt,lower,upper):
18 """ Εμφανίζει μια προτροπή και μετά διαβάζει
19 από το χρήστη κι επιστρέφει έναν ακέραιο αριθμό,
20 εξασφαλίζοντας ότι βρίσκεται εντός ορίων.
21 prompt: η προτροπή που εμφανίζεται στο χρήστη
22 lower, upper: τα όρια
23 """
24 # εμφάνιση προτροπής και ανάγνωση τιμής
25 print(prompt, end="")
26 num = int(input())
27 # επανάληψη, όσο η τιμή είναι εκτός ορίων
28 while num < lower or num > upper:
29 # μήνυμα λάθους
30 print("Δώστε τιμή από",lower,"μέχρι",upper)
31 # εμφάνιση προτροπής και ανάγνωση τιμής
32 print(prompt, end="")
33 num = int(input())
34 # η έγκυρη τιμή επιστρέφεται
35 return num

36 def isLeap(year):
37 """ Επιστρέφει την τιμή True αν το έτος year
38 είναι δίσεκτο, αλλιώς επιστρέφει False.
39 """
40 return ((year % 4 == 0 and year % 100 > 0) or
41 year % 400 == 0)

42 def daysOfMonth(m,y):
43 """ Επιστρέφει το πλήθος των ημερών ενός μήνα.
44 m: ο αριθμός του μήνα (1-12)
45 y: ο αριθμός του έτους (απαραίτητο όταν m=2)
46 """
47 if m == 2:
48 # Φεβρουάριος
49 if isLeap(y):
50 return 29
51 else:
52 return 28
53 elif m <= 7:
54 # Ιανουάριος - Ιούλιος (πλην Φεβρουαρίου)
55 if m % 2 == 1:
56 return 31
57 else:
58 return 30
59 else:
60 # Αύγουστος - Δεκέμβριος
61 if m % 2 == 1:
62 return 30
63 else:
64 return 31
Η ΜΕΡΑ ΤΗΣ ΕΒΔΟΜΑΔΑΣ 10

65 # είσοδος ημερομηνίας από το χρήστη


66 year = readInt("Έτος: ", 1923, 2999)
67 month = readInt("Mήνας: ", 1, 12)
68 day = readInt("Ημέρα: ", 1, daysOfMonth(month,year))

69 # κλήση υποπρογράμματος για υπολογισμό ημέρας


70 weekday = computeWeekday(day, month, year)
71 # εμφάνιση ημέρας της εβδομάδας (0:Κυριακή - 6:Σάββατο)
72 print("Ημέρα της εβδομάδας:", weekday)
src/weekday.final.py

Δομες Δεδομενων Οργανωμένες συλλογές τιμών όπως οι πλειάδες ονομάζονται δομές δεδομένων. Μια δομή
δεδομένων δεν είναι ένα απλό σύνολο από τιμές, δεν αποτελεί μια απλή γενίκευση των μεταβλητών, αλλά είναι
δομημένη με συγκεκριμένο τρόπο. Τα διαφορετικά είδη δομών δεδομένων διαφοροποιούνται κυρίως από τον
τρόπο με τον οποίο τα δεδομένα αποθηκεύονται, ανακτώνται και συσχετίζονται μεταξύ τους.
Διαπλανητικό Κουίζ 5
Στο κεφάλαιο αυτό θα υλοποιήσουμε ένα παιχνίδι ερωτήσεων με θέμα τους
πλανήτες του ηλιακού μας συστήματος. Ο παίκτης θα καλείται να βρει
έναν πλανήτη από τη θέση του στο ηλιακό σύστημα, να τον μαντέψει από 17 Απριλίου 2016

τους γειτονικούς του πλανήτες ή να βάλει τους πλανήτες σε σωστή σειρά, 22:55

Οι ερωτήσεις που θα τίθενται στον παίκτη δεν θα είναι οι ίδιες κάθε φορά,
αλλά θα δημιουργούνται δυναμικά, κατά την εκτέλεση του προγράμματος.
Μέσα από το πρόγραμμά μας θα γνωρίσουμε μια πολύ σημαντική έννοια
στον προγραμματισμό, τη δομή δεδομένων και πιο συγκεκριμένα μια δομή
δεδομένων που ονομάζεται λίστα. Θα έχουμε επίσης την ευκαιρία να εξοι-
κειωθούμε ακόμη περισσότερο με τα υποπρογράμματα, μιας και όλο το
πρόγραμμα υλοποιείται με τη βοήθειά τους.
Έννοιες: λίστες, υποπρογράμματα

Όλοι οι Καλοί Χωράνε


Πού θα καταχωρίσω τα ονόματα όλων των πλανητών του ηλιακού συ-
στήματος;
Για να μπορεί το πρόγραμμά μας να “δημιουργεί” ερωτήσεις προς planets
τον χρήστη θα πρέπει να έχει διαθέσιμα τα ονόματα όλων των πλα-
νητών. Μέχρι στιγμής χρειάστηκε να αποθηκεύσουμε τιμές και να 0 Ερμής
αναφερθούμε σε αυτές χρησιμοποιώντας μεταβλητές. Πολύ συχνά
όμως χρειάζεται να αποθηκεύσουμε μια ομάδα τιμών με τη μορφή 1 Αφροδίτη
μιας συλλογής. Με τον τρόπο αυτό θα έχουμε πρόσβαση σε όλα τα 2 Γη
στοιχεία της ομάδας χρησιμοποιώντας ένα κοινό όνομα. Ένα αντι-
κείμενο που μας δίνει αυτή τη δυνατότητα είναι η λίστα. 3 Άρης
Ας κατασκευάσουμε λοιπόν μια λίστα με τα ονόματα των πλανητών, 4 Δίας
την οποία θα ονομάσουμε planets.
5 Κρόνος
1 # η λίστα με τα ονόματα όλων των πλανητών
6 Ουρανός
2 planets = ["Ερμής", "Αφροδίτη", "Γη", "Άρης", "Δίας",
3 "Κρόνος", "Ουρανός", "Ποσειδώνας"] 7 Ποσειδώνας
src/planets.1.py

Τα στοιχεία της λίστας καταχωρούνται ακολουθιακά, που σημαίνει Σχήμα 5.1: Η λίστα με τα ονόματα των
πλανητών. Οι θέσεις της λίστας είναι
ότι παίζει ρόλο η σειρά με την οποία τα εισάγουμε. αριθμημένες. Με βάση τους αριθμούς
των θέσεων μπορούμε ν’ αναφερθούμε
σε κάθε στοιχείο ξεχωριστά. Προσοχή
όμως, η αρίθμηση ξεκινά από το 0.

1
ΔΙΑΠΛΑΝΗΤΙΚΟ ΚΟΥΙΖ 2

Η Διαμάχη
Με τον Πλούτωνα τί γίνεται; Μερικοί από εμάς μεγάλωσαν μαθαίνοντας
ότι ο Πλούτωνας είναι πλανήτης!
Ο Πλούτωνας ανακαλύφθηκε το 1930 και μέχρι το 2006 θεωρούνταν
ο ένατος πλανήτης του ηλιακού μας συστήματος. Αν και η Διεθνής
Αστρονομική Ένωση τον έχει πια υποβιβάσει σε πλανήτη-νάνο, η
παράλειψή του από τη λίστα των πλανητών ίσως δημιουργεί σε κά-
ποιους μια συναισθηματική φόρτιση. Το πρόγραμμά μας λοιπόν, εκ-
δηλώνοντας ευαισθησία, θα ρωτά τον χρήστη αν θέλει ο Πλούτωνας
να θεωρηθεί ένας από τους πλανήτες και ανάλογα θα προσθέτει την
τιμή "Πλούτωνας" στη λίστα με τα ονόματα των πλανητών.
Αρχικά, θα υλοποιήσουμε μια συνάρτηση που θα ρωτά τον παίκτη
αν επιθυμεί ο Πλούτωνας να συμπεριληφθεί στους πλανήτες και θα
επιστρέφει την τιμή True ή False ανάλογα με την απάντηση του.
1 def addPluto():
2 ''' Ρωτά αν ο Πλούτωνας θα θεωρηθεί πλανήτης
3 κι ανάλογα επιστρέφει την τιμή True ή False
4 '''
5 # ερώτηση στον παίκτη για τον Πλούτωνα
6 print("Nα θεωρήσουμε τον Πλούτωνα πλανήτη (ν/ο);")
7 answer = input()
8 # επιστροφή τιμής ανάλογα με την aπάντηση
9 return answer == "Ν" or answer == "ν"
Η τελευταία γραμμή της συνάρτησης επιστρέφει την τιμή της συν-
θήκης. Είναι ένας αμεσότερος τρόπος να κάνουμε το εξής: 5 Κρόνος

# επιστροφή τιμής ανάλογα με την απάντηση 6 Ουρανός


if answer == "Ν" or answer == "ν": 7 Ποσειδώνας
return True
else: 8 Πλούτωνας
return False
Σχήμα 5.2: Η λίστα των πλανητών,
Το κύριο πρόγραμμα θα καλεί τη συνάρτηση addPluto() και θα μετά από την προσθήκη ενός ακόμα
προσαρτά τον Πλούτωνα στη λίστα, εφόσον η απάντηση του παίκτη στοιχείου στο τέλος της.
είναι θετική.
13 # ερώτηση για προσθήκη του Πλούτωνα Η μέθοδος append() εφαρμόζεται σε
μια λίστα και προσθέτει ένα νέο στοι-
14 if addPluto(): χείο στο τέλος της. Η αντίστροφη μέ-
15 # προσάρτηση του Πλούτωνα στο τέλος της λίστας θοδος είναι η pop(), η οποία εφαρ-
16 planets.append("Πλούτωνας") μόζεται σε μια λίστα κι επιστρέφει το
src/planets.2.py τελευταίο στοιχείο, διαγράφοντάς το
από τη λίστα.
Παρατηρούμε ότι μπορούμε να προσθέτουμε (ή και να αφαιρούμε)
στοιχεία κατά βούληση, τροποποιώντας το μέγεθος της λίστας, δη-
λαδή το πλήθος των στοιχείων της.
ΔΙΑΠΛΑΝΗΤΙΚΟ ΚΟΥΙΖ 3

Πες Μου Πως Σε Λένε Να Σου Πω Που Είσαι


Το παιχνίδι πρέπει να ξεκινά με ένα απλό ερώτημα. Να βρίσκει, για πα-
ράδειγμα, ο παίκτης τη θέση ενός πλανήτη στο ηλιακό σύστημα.
Οι ερωτήσεις προς το παίκτη δεν θα είναι πάντα ακριβώς οι ίδιες.
Η δημιουργία των ερωτήσεων θα περιλαμβάνει ένα στοιχείο τυχαιό-
τητας, συνήθως την επιλογή ενός από τους πλανήτες. Για τον σκοπό
αυτό, θα πρέπει να εισάγουμε την κατάλληλη βιβλιοθήκη.
1 import random
Θα υλοποιούμε κάθε ερώτημα με χρήση συναρτήσεων. Έτσι θα δια-
τηρούμε τον κώδικα μας ξεκάθαρο, αλλά και θα ξαναχρησιμοποιούμε,
όπου είναι δυνατόν, τα ίδια υποπρογράμματα.
Για το πρώτο ερώτημα θα υλοποιήσουμε μια συνάρτηση η οποία
δέχεται ως παράμετρο τη λίστα των πλανητών, επιλέγει από αυτή
τυχαία έναν πλανήτη και, αφού εμφανίσει το όνομά του στο χρήστη,
του ζητά να καθορίσει τη θέση του στο ηλιακό σύστημα.
Ένας τρόπος να επιλεχθεί τυχαία ένας πλανήτης είναι να επιλεχθεί
πρώτα ένας τυχαίος ακέραιος που θα αντιστοιχεί στη θέση του πλα-
νήτη μέσα στη λίστα. Η θέση αυτή θα χρησιμοποιηθεί για να προσ-
διορίστει και το όνομα του πλανήτη.
Η συνάρτηση len() επιστρέφει το
11 def findByName(planets):
πλήθος των στοιχείων της λίστας.
12 ''' Εμφανίζει το όνομα ενός πλανήτη και ζητά
13 από τον παίκτη τη θέση του στο ηλιακό σύστημα.
14 planets: λίστα με τα ονόματα όλων των πλανητών
15 '''
16 # επιλογή θέσης τυχαίου πλανήτη
17 nbPlanets = len(planets)
18 position = random.randint(0, nbPlanets - 1)
Η επιλογή της τυχαίας θέσης χρειάζεται προσοχή. Οι πιθανές τιμές Μπορούμε ν’ αναφερθούμε στα στοι-
για την μεταβλητή position κυμαίνονται ανάμεσα στο μηδέν και το χεία μιας λίστας με βάση τη θέση
τους σε αυτή. Η αρίθμηση των θέ-
πλήθος των στοιχείων της λίστας μειωμένο κατά ένα. Αυτό συμβαίνει σεων ξεκινά από το 0. Έτσι, εδώ το
γιατί η αρίθμηση των θέσεων σε μια λίστα ξεκινά από το μηδέν. πρώτο στοιχείο είναι το planets[0]
και αντιστοιχεί στην τιμή "Ερμής", το
Τώρα γνωρίζουμε ότι ο πλανήτης που έχουμε επιλέξει βρίσκεται στη δεύτερο είναι το planets[1] και αντι-
θέση position. Το όνομα του πλανήτη βρίσκεται στην αντίστοιχη στοιχεί στην τιμή "Αφροδίτη", κ.ο.κ.
θέση μέσα στη λίστα planets. Οι θέσεις μιας λίστας αριθμούνται και
19 # αναφορά στο όνομα του πλανήτη αντίστροφα, ξεκινώντας από το τέ-
λος, οπότε π.χ. στο τελευταίο στοι-
20 # μέσω της θέσης του στη λίστα χείο μπορούμε να αναφερθούμε και ως
21 planet = planets[position] planets[-1].
Στη συνέχεια η συνάρτηση θα εμφανίζει την ερώτηση στον παίκτη Για να αναφερθούμε σ’ ένα στοιχείο
και θα ζητάει την απάντησή του. της λίστας μπορούμε να χρησιμοποι-
ήσουμε ανάμεσα στις αγκύλες οποια-
22 # ερώτηση στον παίκτη δήποτε ακέραια έκφραση, όπως εδώ
23 print("Σε ποια σειρά βρίσκεται ο πλανήτης ", την position. Η τιμή της έκφρασης
καθορίζει τη θέση του στοιχείου της
24 planet, " σε σχέση με τον Ήλιο;", sep = "") λίστας στο οποίο αναφερόμαστε.
25 answer = int(input())
ΔΙΑΠΛΑΝΗΤΙΚΟ ΚΟΥΙΖ 4

Η απάντηση θα πρέπει να ελεγχθεί και να εμφανίζεται είτε μήνυμα


επιβράβευσης είτε η σωστή απάντηση, σε περίπτωση αποτυχίας.
Η παράμετρος sep της print καθορί-
26 # έλεγχος της απάντησης του παίκτη
ζει τι θα εμφανίζεται ανάμεσα στα επι-
27 if answer == position + 1: μέρους μηνύματα της print. Η προ-
28 # αν η απάντηση είναι σωστή καθορισμένη τιμή της είναι η " ", γι’
29 print("Μπράβο, το βρήκες!") αυτό κανονικά εμφανίζεται ένα κενό.
30 else: Αν επιθυμούμε να αλλάξει αυτή η προ-
καθορισμένη συμπεριφορά τότε αρκεί
31 # εμφάνιση της σωστής απάντησης να επανορίσουμε την sep.
32 print("Ο πλανήτης ", planet, " είναι ο ",
33 position + 1, "ος πλανήτης.", sep = "")
Εδώ υπάρχει και πάλι ένα λεπτό σημείο. Η θέση ενός πλανήτη στο
ηλιακό σύστημα είναι κάποια τιμή από το 1 μέχρι και το πλήθος
των πλανητών. Αντίθετα, η θέση του στη λίστα είναι κάποια τιμή
από το 0 μέχρι και το πλήθος των πλανητών μειωμένο κατά 1. Η
διαφορά αυτή οφείλεται στο γεγονός ότι οι άνθρωποι ξεκινούν την
αρίθμηση θέσεων από το 1, ενώ στις λίστες η αρίθμηση ξεκινά από
το 0. Κατά συνέπεια, η θέση ενός πλανήτη στο ηλιακό σύστημα είναι
κατά μια μονάδα μεγαλύτερη από τη θέση του στη λίστα και γι’ αυτό
χρειάζεται να προσθέσουμε μια μονάδα στη μεταβλητή position
πριν την συγκρίνουμε με την απάντηση answer του παίκτη.
Ώρα να ξεκινήσουμε το παιχνίδι, εμφανίζοντας το πρώτο ερώτημα
στον παίκτη.
Το \n είναι ένας “κωδικός” που ανα-
41 # εύρεση του πλανήτη από το όνομα
παριστά την αλλαγή γραμμής. Εδώ
42 print("\nΕρώτηση 1:") θέλουμε να υπάρχει μια κενή γραμμή
43 findByName(planets) ανάμεσα στο μήνυμα που θα εμφανι-
src/planets.3.py στεί και τις προηγούμενες γραμμές.

Πες Μου Που Είσαι Να Σου Πω Πως Σε Λένε


Γίνεται ν’ αντιστρέψουμε τώρα το ερώτημα; Να δίνεται στο χρήστη η
θέση ενός πλανήτη και να ζητείται τ’ όνομά του.
Θα υλοποιήσουμε μια ακόμα συνάρτηση, παρόμοια με την προηγού-
μενη, η οποία επιλέγει τυχαία έναν πλανήτη και, αφού εμφανίσει το
ερώτημα στον παίκτη, ζητά και ελέγχει την απάντησή του.
34 def findByPosition(planets):
35 ''' Εμφανίζει τη θέση ενός πλανήτη στο ηλιακό
36 σύστημα και ζητά από τον παίκτη το όνομά του.
37 planets: λίστα με τα ονόματα όλων των πλανητών
38 '''
39 # επιλογή τυχαίου πλανήτη
40 nbPlanets = len(planets)
41 position = random.randint(0, nbPlanets - 1)
42 planet = planets[position]
ΔΙΑΠΛΑΝΗΤΙΚΟ ΚΟΥΙΖ 5

43 # ερώτηση στον παίκτη


44 print("Ποιος είναι ο ", position + 1,
45 "ος πλανήτης μετά τον Ήλιο;", sep = "")
46 answer = input()
47 # έλεγχος της απάντησης του παίκτη
48 if answer == planet:
49 # αν η απάντηση είναι σωστή
50 print("Μπράβο, το βρήκες!")
51 else:
52 # εμφάνιση της σωστής απάντησης
53 print("Είναι ο πλανήτης ", planet, ".", sep = "")
src/planets.4.py

Τι θα γίνει όμως σε περίπτωση που ο παίκτης γράψει το όνομα του


σωστού πλανήτη με κεφαλαία γράμματα ή αφήσει κενά στην απά-
ντησή του; Παρόλο που ο παίκτης θα έχει ουσιαστικά απαντήσει
σωστά, το πρόγραμμά μας θα του εμφανίζει το αντίθετο, αφού για
να θεωρηθεί η απάντηση σωστή θα πρέπει να είναι πανομοιότυπη με
το όνομα του πλανήτη, όπως εμφανίζεται στη λίστα των πλανητών.
Ένα μικρό και πολύ συνηθισμένο τρικ για να παρακάμψουμε αυτό
το πρόβλημα είναι να συγκρίνουμε την απάντηση του παίκτη και
το όνομα του σωστού πλανήτη αφού πρώτα τα μετατρέψουμε σε
πεζά γράμματα, διαγράφοντας μάλιστα και τυχόν κενά που υπάρ-
χουν στην αρχή ή στο τέλος της απάντησης.
if answer.lower().strip() == name.lower(): Η μέθοδος lower() εφαρμόζεται σε
μια αλφαριθμητική τιμή και μετατρέ-
Μετατρέποντας τις δύο τιμές σε μια κοινή αναπαράσταση πριν τις πει τους χαρακτήρες της σε πεζούς. Η
συγκρίνουμε μετριάζουμε σε σημαντικό βαθμό το πρόβλημα. Έχετε αντίστροφη μέθοδος είναι η upper().
όμως υπόψη ότι δεν είναι και τόσο απλό να λυθεί εντελώς. Για παρά- H μέθοδος strip() εφαρμόζεται σε
δειγμα, αν ο παίκτης κάνει κάποιο ορθογραφικό λάθος ή δεν τονίσει μια αλφαριθμητική τιμή και αφαιρεί
τα κενά που πιθανώς να βρίσκονται
την απάντησή του τότε και πάλι δεν θα θεωρηθεί σωστή. στην αρχή και το τέλος της.
Στο κύριο πρόγραμμα, μπορούμε να προσθέσουμε τη νέα ερώτηση
στο παιχνίδι μας.
64 # εύρεση του πλανήτη από τη θέση
65 print("\nΕρώτηση 2:")
66 findByPosition(planets)
src/planets.4.py

Η Δυσκολία Στη Ζωή Είναι Να Διαλέξεις


Όταν ο παίκτης πληκτρολογεί τις απαντήσεις του μπαίνουμε σε μπελά-
δες. Ας κάνουμε τις ερωτήσεις πολλαπλής επιλογής.
Μια ερώτηση πολλαπλής επιλογής συνοδεύεται από ένα πλήθος
προκαθορισμένων απαντήσεων, από τις οποίες ο χρήστης επιλέγει
εκείνη που θεωρεί σωστή. Για να παράγουμε μια ερώτηση πολλα-
πλής επιλογής, θα πρέπει αρχικά να γνωρίζουμε ποιες είναι οι πι-
θανές απαντήσεις, πόσες από αυτές θα προσφερθούν ως επιλογές
στο χρήστη και, φυσικά, ποια είναι η σωστή απάντηση.
ΔΙΑΠΛΑΝΗΤΙΚΟ ΚΟΥΙΖ 6

Θα μπορούσαμε λοιπόν απευθείας να υλοποιήσουμε μια συνάρτηση


η οποία δέχεται τις παραπάνω παραμέτρους και παράγει μια ερώ-
τηση πολλαπλής επιλογής.
Ωστόσο, η παραγωγή μιας ερώτησης πολλαπλής επιλογής περιλαμ-
βάνει συγκεκριμένα στάδια, μερικά από τα οποία δεν είναι τετριμ-
μένα και μπορούν να υλοποιηθούν με αρκετούς διαφορετικούς τρό-
πους. Για τον σκοπό αυτό, κάθε στάδιο (εκτός από το τελευταίο που
είναι απλό) θα υλοποιηθεί σε ξεχωριστό υποπρόγραμμα.

ερώτηση πολλαπλής επιλογής


multipleChoice(nbChoices, possible, correct)

επιλογή απαντήσεων
select(nbChoices, possible, correct)

προβολή απαντήσεων
showMultiple(choices)

ανάγνωση έγκυρης απάντησης


readAnswer(nbChoices)

έλεγχος απάντησης και


εμφάνιση αποτελέσματος

Σχήμα 5.3: Το πρόβλημα της παραγωγής μιας ερώτησης πολλαπλής επιλογής ανα-
λύεται σε μικρότερα προβλήματα. Στην αναπαράσταση αυτή φαίνονται και τα
υποπρογράμματα που θα δημιουργηθούν στη συνέχεια για τα προβλήματα αυτά.

Για την επιλογή των πιθανών απαντήσεων θα υλοποιήσουμε μια συ-


νάρτηση που δέχεται τη λίστα με όλες τις πιθανές απαντήσεις, κα-
θώς και την σωστή απάντηση, επιλέγει τυχαία ένα ορισμένο πλήθος
από αυτές και τις επιστρέφει σε μια λίστα, ώστε να μπορούν στη συ-
νέχεια να εμφανιστούν στο χρήστη.
Η συνάρτηση sample() της βιβλιο-
34 def select(nbChoices, possible, correct):
θήκης random δέχεται σαν παράμετρο
35 ''' Επιλέγει από τη λίστα πιθανών απαντήσεων μια λίστα και το πλήθος των τιμών
36 ένα ορισμένο πλήθος και τις επιστρέφει σε λίστα. που θα επιλεχθούν από αυτή και επι-
37 nbChoices: πλήθος απαντήσεων που θα επιλεχθούν στρέφει μια νέα λίστα που περιέχει μια
38 possible: λίστα όλων των πιθανών απαντήσεων, τυχαία επιλογή στοιχείων της πρώ-
της. Αν θέλουμε να επιλέξουμε τυχαία
39 πρέπει len(possible) >= nbChoices μόνο ένα στοιχείο μιας λίστας, χρησι-
40 correct: η σωστή απάντηση μοποιούμε την συνάρτηση choice(),
41 ''' της ίδιας βιβλιοθήκης.
42 # διάλεξε τυχαία απαντήσεις
43 answers = random.sample(possible, nbChoices)
Επειδή κάνουμε τα πρώτα μας βήματα στη διαχείριστη λιστών, χρη-
σιμοποιήσαμε έναν “έτοιμο” τρόπο να επιλέξουμε ένα υποσύνολο
των πιθανών απαντήσεων. Υπάρχουν και αρκετοί εναλλακτικοί τρό-
ποι τους οποίους θα μπορέσετε να δοκιμάσετε στις ασκήσεις.
Η συνάρτηση δεν έχει ακόμα ολοκληρωθεί. Στη λίστα answers των
απαντήσεων που θα επιλεχθούν θα πρέπει να περιέχεται οπωσδή-
ποτε και η σωστή απάντηση, κάτι το οποίο δεν έχουμε εξασφαλίσει.
ΔΙΑΠΛΑΝΗΤΙΚΟ ΚΟΥΙΖ 7

Σε περίπτωση που η σωστή απάντηση δεν περιέχεται στην answers,


θα πρέπει να εισαχθεί σε μια τυχαία θέση της λίστας, αντικαθιστώ-
ντας το στοιχείο που ήδη βρίσκεται εκεί.
Ο τελεστής in ελέγχει αν μια τιμή
44 # αν η σωστή απάντηση δεν υπάρχει στη λίστα
υπάρχει σε μια λίστα και επιστρέφει
45 if correct not in answers: αντίστοιχα την τιμή True ή False.
46 # διάλεξε τυχαία θέση για την σωστή απάντηση
Χρησιμοποιώντας μια εντολή όπως
47 index = random.randint(0, nbChoices - 1) η answers[index] = correct τροπο-
48 # εισαγωγή της σωστής απάντησης στη λίστα ποιείται ένα στοιχείο της λίστας, κα-
49 answers[index] = correct θώς μια νέα τιμή αντικαθιστά την
50 # επιστροφή λίστας απαντήσεων προηγούμενη. Είναι ένα βασικό χαρα-
κτηριστικό των λιστών ότι τα στοι-
51 return answers χεία τους μπορούν να τροποποιηθούν.
Για την εμφάνιση των εναλλακτικών επιλογών θα υλοποιήσουμε μια
συνάρτηση που δέχεται σαν παράμετρο τη λίστα με τις πιθανές
απαντήσεις και τις εμφανίζει αριθμημένες στον παίκτη.
Η εντολή for είναι μια εντολή επα-
52 def showMultiple(answers):
νάληψης που διατρέχει τα στοιχεία
53 ''' Εμφανίζει αριθμημένες τις πιθανές απαντήσεις μιας ακολουθίας τιμών, όπως μια λί-
54 μιας ερώτησης, σε στυλ πολλαπλής επιλογής. στα ή οι χαρακτήρες μιας αλφαριθμη-
55 answers: λίστα απαντήσεων τικής τιμής, με τη σειρά με την οποία
56 ''' αυτά εμφανίζονται στην ακολουθία.
Σε κάθε επανάληψη η τιμή του επό-
57 number = 1 μενου στοιχείου της ακολουθίας ανα-
58 # σάρωση λίστας πιθανών απαντήσεων τίθεται στη μεταβλητή απαρίθμησης
59 for answer in answers: που χρησιμοποιούμε στη for.
60 print("(", number, ") ", answer, Όπως και στην εντολή while έτσι
61 sep = "", end = " ") και στη for στοιχίζουμε δεξιότερα τις
62 number = number + 1 εντολές που περιέχονται σε αυτήν.

Μια ακόμα συνάρτηση θα ζητάει την απάντηση του παίκτη, θα ελέγ-


χει ότι είναι έγκυρη, δηλαδή εντός του πλήθους των πιθανών επιλο-
γών, και θα την επιστρέφει.
63 def readAnswer(nbChoices):
64 ''' Zητάει την απάντηση του παίκτη,
65 μέχρι να είναι έγκυρη, και την επιστρέφει.
66 nbChoices: πλήθος έγκυρων απαντήσεων
67 '''
68 # επανάληψη: μέχρι να δοθεί έγκυρη απάντηση
69 while True:
70 # ανάγνωση απάντησης
71 choice = int(input())
72 # έλεγχος εγκυρότητας
73 if choice < 1 or choice > nbChoices:
74 print("Επίλεξε μια έγκυρη απάντηση",
75 "από 1 μέχρι", nbChoices, sep = "")
76 else:
77 break
78 # η απάντηση επιστρέφεται
79 return choice
ΔΙΑΠΛΑΝΗΤΙΚΟ ΚΟΥΙΖ 8

Η τελευταία συνάρτηση που θα υλοποιήσουμε θα καλεί όλες τις


προηγούμενες και θα ελέγχει αν η απάντηση που έδωσε ο παίκτης
είναι σωστή ή λανθασμένη.
80 def multipleChoice(nbChoices, possible, correct):
81 ''' Ερώτηση πολλαπλής επιλογής: εμφανίζει τις
82 πιθανές απαντήσεις, ζητάει την επιλογή του παίκτη
83 και ελέγχει αν είναι σωστή ή λανθασμένη.
84 nbChoices: πλήθος απαντήσεων που θα εμφανιστούν
85 possible: λίστα όλων των πιθανών απαντήσεων
86 correct: η σωστή απάντηση
87 '''
88 # επιλογή απαντήσεων
89 answers = select(nbChoices, possible, correct)
90 # προβολή απαντήσεων
91 showMultiple(answers)
92 # ανάγνωση έγκυρης απάντησης
93 playerChoice = readAnswer(nbChoices)
94 # έλεγχος και εμφάνιση αποτελέσματος
95 if answers[playerChoice - 1] == correct:
96 print("Μπράβο, το βρήκες!")
97 else:
98 print("H σωστή απάντηση είναι: ",
99 correct, ".", sep = "")
Μια σημαντική παρατήρηση είναι ότι όλα τα προηγούμενα υποπρο-
γράμματα που υλοποιήσαμε είναι τελείως ανεξάρτητα από το πρό-
γραμμα με τους πλανήτες. Μπορούμε να τα χρησιμοποιήσουμε για
να εμφανίζουμε σε ένα πρόγραμμα ερωτήσεις τύπου πολλαπλής επι-
λογής ανεξάρτητα από το περιεχόμενό τους.
Πλέον μπορούμε να τροποποιήσουμε την συνάρτηση που αντιστοι-
χεί στο δεύτερο ερώτημα. Το αρχικό τμήμα που επιλέγει έναν τυ-
χαίο πλανήτη και θέτει το ερώτημα στον παίκτη είναι ακριβώς το
ίδιο, αλλά ακολουθείται από την κλήση της συνάρτησης για τη δη-
μιουργία ερώτησης πολλαπλής επιλογής.
100 def findByPosition(planets):
101 ''' Εμφανίζει τη θέση ενός πλανήτη στο ηλιακό
102 σύστημα και ζητά από τον παίκτη το όνομά του.
103 planets: λίστα με τα ονόματα όλων των πλανητών
104 '''
105 # επιλογή τυχαίου πλανήτη
106 nbPlanets = len(planets)
107 position = random.randint(0, nbPlanets - 1)
108 planet = planets[position]
109 # ερώτηση πολλαπλής επιλογής στον παίκτη
110 print("Ποιος είναι ο ", position + 1,
111 "ος πλανήτης μετά τον Ήλιο;", sep = "")
112 multipleChoice(4, planets, planet)
src/planets.5.py
ΔΙΑΠΛΑΝΗΤΙΚΟ ΚΟΥΙΖ 9

Παρατηρήστε τις παραμέτρους με τις οποίες καλούμε την συνάρ-


τηση multipleChoice(). Καθορίζουν ότι θα εμφανιστούν στο χρή-
στη 4 επιλογές, ενώ η λίστα των πιθανών απαντήσεων περιλαμβάνει
όλους τους πλανήτες. Ως σωστή απάντηση προσδιορίζεται βέβαια
το όνομα του πλανήτη που έχει επιλεχθεί.

Πες Μου Το Γείτονά Σου Να Σου Πω Ποιος Είσαι


Θα μπορούσαμε να ζητάμε από τον παίκτη να προσδιορίσει έναν πλανήτη
από τους γειτονικούς του πλανήτες;
Για το ερώτημα αυτό θα υλοποιήσουμε μια συνάρτηση η οποία δέ-
χεται σαν παράμετρο τη λίστα των πλανητών, εμφανίζει τους γεί-
τονες ενός πλανήτη και ζητά από τον παίκτη να προσδιορίσει για
ποιον πλανήτη πρόκειται. Θα ξεκινήσουμε τη συνάρτηση επιλέγο-
ντας όπως πάντα έναν τυχαίο πλανήτη.
113 def findByNeighbours(planets):
114 ''' Ζητά από τον παίκτη να βρει έναν πλανήτη,
115 εμφανίζοντας τους γειτονικούς του πλανήτες.
116 planets: λίστα με τα ονόματα όλων των πλανητών
117 '''
118 # επιλογή τυχαίου πλανήτη
119 nbPlanets = len(planets)
120 position = random.randint(0, nbPlanets - 1)
121 planet = planets[position]
Ο προηγούμενος πλανήτης βρίσκεται στη θέση position - 1 και
ο επόμενος στη θέση position + 1. Τί γίνεται όμως αν έχει επιλε-
χθεί ο πρώτος πλανήτης στη θέση 0; Τότε δεν έχει νόημα η αναφορά
στη θέση position - 1. Ομοίως, αν ο πλανήτης βρίσκεται στην τε-
λευταία θέση της λίστας τότε δεν έχει νόημα η αναφορά στη θέση
position + 1. Το πρόγραμμά μας θα πρέπει να εξετάζει το ενδεχό-
μενο ο πλανήτης να βρίσκεται στα άκρα του ηλιακού συστήματος
και να προσαρμόζει κατάλληλα το ερώτημα προς τον παίκτη.
122 # ερώτημα προς τον παίκτη
123 print("Ποιος πλανήτης", end = " ")
124 # έλεγχος για την ύπαρξη γειτόνων
125 if position == 0:
126 # υπάρχει μόνο επόμενος πλανήτης
127 next = planets[position + 1]
128 print("βρίσκεται πριν από τον πλανήτη ",
129 next, ";", sep = "")
130 elif position == nbPlanets - 1:
131 # υπάρχει μόνο προηγούμενος πλανήτης
132 previous = planets[position - 1]
133 print("βρίσκεται μετά από τον πλανήτη ",
134 previous, ";", sep = "")
ΔΙΑΠΛΑΝΗΤΙΚΟ ΚΟΥΙΖ 10

135 else:
136 # υπάρχουν και οι δύο γειτονικοί πλανήτες
137 next = planets[position + 1]
138 previous = planets[position - 1]
139 print("βρίσκεται ανάμεσα στους πλανήτες ",
140 previous, " και ", next, ";", sep = "")
Και αυτό το ερώτημα θα είναι πολλαπλής επιλογής, επομένως θα γί-
νει χρήση των υποπρογραμμάτων που υλοποιήσαμε προηγουμένως.
Θα εμφανιστούν 4 επιλογές από τη λίστα των πλανητών, συμπερι-
λαμβανομένης και της σωστής.
141 # πολλαπλή επιλογή
142 multipleChoice(4, planets, planet)
Πλέον μπορούμε να εμφανίσουμε το ερώτημα στον παίκτη.
156 # εύρεση του πλανήτη από τους γείτονές του
157 print("\nΕρώτηση 3:")
158 findByNeighbours(planets)
src/planets.6.py

Παίζοντας, θα διαπιστώσουμε ότι το πρόγραμμά μας δεν λειτουργεί


ακριβώς όπως θα έπρεπε. Μερικές φορές οι γειτονικοί πλανήτες εμ-
Η μέθοδος copy() εφαρμόζεται σε
φανίζονται τόσο στην ερώτηση όσο και στις πιθανές απαντήσεις. μια λίστα κι επιστρέφει ένα αντί-
Θα πρέπει να διορθώσουμε τη συνάρτηση έτσι ώστε η λίστα πιθα- γραφό της. Εδώ η δημιουργία αντι-
νών απαντήσεων να μην είναι ολόκληρη η λίστα των πλανητών, γράφου είναι απαραίτητη γιατί δεν
όπως είναι τώρα, αλλά ένα αντίγραφό της από το οποίο θα έχουμε θέλουμε να τροποποιηθεί η αρχική λί-
στα planets.
αφαιρέσει τους γείτονες του ζητούμενου πλανήτη.
Ας ξεκινήσουμε κατασκευάζοντας το αντίγραφο, το οποίο είναι απα- planets
ραίτητο προκειμένου να μην αλλοιωθεί η αρχική λίστα πλανητών.
answers
122 # πιθανές απαντήσεις: αντίγραφο λίστας πλανητών
123 answers = planets.copy() Σχήμα 5.4: Η δημιουργία αντιγράφου
Τώρα, στα σημεία όπου προσδιορίζουμε τους γειτονικούς πλανήτες με την answers = planets.copy()
εξασφαλίζει ότι οποιαδήποτε τρο-
θα πρέπει να φροντίζουμε ώστε αυτοί να αφαιρούνται από τη λίστα ποποίηση της answers αφήνει την
πιθανών απαντήσεων. planets αμετάβλητη, αφού πρόκειται
για διαφορετικές λίστες.
128 # υπάρχει μόνο επόμενος πλανήτης
129 next = planets[position + 1]
planets
130 answers.remove(next) answers
134 # υπάρχει μόνο προηγούμενος πλανήτης Σχήμα 5.5: Αντίθετα, με την εντολή
135 previous = planets[position - 1] answers = planets θα δίναμε απλά
136 answers.remove(previous) δύο ονόματα στην ίδια λίστα. Οποια-
δήποτε αλλαγή στη λίστα αντικατο-
140 # υπάρχουν και οι δύο γειτονικοί πλανήτες πτρίζεται και στα δύο ονόματα.
141 next = planets[position + 1] Η μέθοδος remove() εφαρμόζεται σε
142 previous = planets[position - 1] μια λίστα. Δέχεται σαν παράμετρο
143 answers.remove(next) ένα στοιχείο της λίστας και το αφαι-
ρεί απ’ αυτή. Αν το στοιχείο δεν υπάρ-
144 answers.remove(previous) χει στη λίστα τότε προκύπτει σφάλμα.
ΔΙΑΠΛΑΝΗΤΙΚΟ ΚΟΥΙΖ 11

Οι πιθανές απαντήσεις στην ερώτηση πολλαπλής επιλογής θα πρέ-


πει πλέον να προέρχονται από τη νέα λίστα answers.
147 # πολλαπλή επιλογή
148 multipleChoice(4, answers, planet)
src/planets.7.py

Here Comes The Sun


Κάτι άλλο που θα μπορούσαμε να ζητάμε από τον παίκτη είναι να επιλέξει
ποιος από τους πλανήτες που του δίνονται είναι πιο κοντά στον Ήλιο.
Η συνάρτηση που θα υλοποιήσουμε για το σκοπό αυτό θα ξεκινά
επιλέγοντας τυχαία τον πλανήτη που θα αποτελεί την σωστή απά- L
ντηση, δηλαδή εκείνον που θα βρίσκεται πιο κοντά στον Ήλιο. Για
να υπάρχουν τουλάχιστον τρεις ακόμα πλανήτες μετά τον αρχικό, L[a:b]
η επιλογή του δεν θα γίνεται σε όλο το εύρος των στοιχείων της
λίστας, αλλά σε ένα πιο περιορισμένο διάστημα. a

149 def closestSun(planets):


150 ''' Εμφανίζει τα ονόματα 4 πλανητών και καλεί τον
151 παίκτη να επιλέξει τον κοντινότερο στον Ήλιο.
152 planets: λίστα με τα ονόματα όλων των πλανητών b
153 '''
154 # επιλογή τυχαίου πλανήτη
155 # εξαιρούνται οι τρεις τελευταίοι πλανήτες Σχήμα 5.6: Το τμήμα L[a:b] είναι μια
156 nbPlanets = len(planets) νέα λίστα που προκύπτει από τον
157 position = random.randint(0, nbPlanets - 4) τεμαχισμό της λίστας L. Ο τεμαχι-
σμός σταματά πριν το στοιχείο στη
158 planet = planets[position] θέση b. Αν παραλειφθεί η θέση εκκί-
Για να συμπληρωθεί η ερώτηση πρέπει να δημιουργήσουμε τη λίστα νησης τότε ο τεμαχισμός ξεκινά από
την αρχή της λίστας. Αν παραλειφθεί
των πιθανών απαντήσεων. Σε αντίθεση με προηγούμενα ερωτήματα, η θέση τερματισμού τότε ο τεμαχισμός
αυτή δεν θα προκύψει από ολόκληρη τη λίστα με τους πλανήτες, σταματά στο τέλος της λίστας, περι-
αλλά μόνο από τον επιλεγμένο πλανήτη κι αυτούς που ακολουθούν. λαμβάνοντας το τελευταίο στοιχείο.

159 # νέα λίστα με τις πιθανές απαντήσεις:


160 # είναι οι πλανήτες από τον επιλεγμένο και μετά planets
161 following = planets[position : ]
Εναλλακτικά, η λίστα following μπορεί να προκύψει ως εξής:
following
following = planets[position : len(planets)]
Ας εμφανίσουμε την ερώτηση στον παίκτη. position
162 # ερώτηση πολλαπλής επιλογής στον παίκτη
163 print("Ποιος πλανήτης είναι πιο κοντά στον Ήλιο;")
164 multipleChoice(4, following, planet)
Ώρα για λίγο παιχνίδι ακόμα.
181 # εύρεση του κοντινότερου στον ήλιο Σχήμα 5.7: Η λίστα following προ-
182 print("\nΕρώτηση 4:") κύπτει από τον τεμαχισμό της λί-
στας των πλανητών με την έκφραση
183 closestSun(planets)
planets[position : ].
src/planets.8.py
ΔΙΑΠΛΑΝΗΤΙΚΟ ΚΟΥΙΖ 12

Ο Παράταιρος
Ας ρωτήσουμε κάτι πιο περίπλοκο. Ας καλέσουμε τον παίκτη να ταξιδέ-
ψει νοερά ανάμεσα σε δύο πλανήτες και να σκεφτεί ποιους πλανήτες θα
συναντήσει στη διαδρομή. Εμείς θα τον καλούμε να βρει έναν πλανήτη
που δεν θα είναι μέρος της διαδρομής.
Θα υλοποιήσουμε μια ακόμα συνάρτηση, στην οποία καταρχήν θα
επιλέγουμε τους δύο πλανήτες που θα αποτελέσουν την αφετηρία
και τον τερματισμό του ταξιδιού.
planets
165 def between(planets): between out
166 ''' Εμφανίζει 4 πλανήτες, από τους οποίους μόνο
167 οι 3 αποτελούν τμήμα μιας διαδρομής. Καλεί τον
168 παίκτη να επιλέξει τον 4ο "παράταιρο" πλανήτη.
169 planets: λίστα με τα ονόματα όλων των πλανητών start
170 '''
171 # τυχαία επιλογή αφετηρίας και τερματισμού
172 nbPlanets = len(planets)
173 start = random.randint(0, nbPlanets - 5)
174 stop = start + 4
stop
Παρατηρήστε ότι η αφετηρία δεν επιλέγεται απ’ όλο το εύρος της
λίστας, αλλά από ένα περιορισμένο διάστημα, ώστε να υπάρχουν
μετά από αυτή τουλάχιστον τέσσερις πλανήτες. Ο τέταρτος από αυ-
τούς είναι ο προορισμός της διαδρομής.
Οι τρεις ενδιάμεσοι πλανήτες τοποθετούνται σε μια λίστα για να Σχήμα 5.8: Η λίστα between των
χρησιμοποιηθούν ως (λανθασμένες) απαντήσεις στην ερώτηση πολ- ενδιάμεσων πλανητών και η λίστα
out των πλανητών που δεν αποτε-
λαπλής επιλογής. Οι πλανήτες που δεν αποτελούν κομμάτι της δια- λούν τμήμα της διαδρομής. Και οι
δρομής συγκεντρώνονται επίσης σε μια λίστα, από την οποία θα δύο προκύπτουν από τεμαχισμούς της
επιλεχθεί τυχαία μια σωστή απάντηση. planets και συνενώσεις.

175 # πλανήτες μεταξύ αφετηρίας και τερματισμού Ο τελεστής + χρησιμοποιείται ανά-


μεσα σε δύο λίστες. Το αποτέλεσμα
176 between = planets[start + 1 : stop] της πράξης είναι μια νέα λίστα που
177 # υπόλοιποι πλανήτες περιέχει όλα τα στοιχεία των αρχικών
178 out = planets[ : start] + planets[stop + 1 : ] με την ίδια σειρά.
179 # τυχαία επιλογή μιας σωστής απάντησης Η συνάρτηση choice() της βιβλιο-
180 correct = random.choice(out) θήκης random δέχεται σαν παράμετρο
μια λίστα κι επιστρέφει ένα τυχαία
Πλέον είμαστε έτοιμοι να εμφανίσουμε την ερώτηση στον παίκτη επιλεγμένο στοιχείο της.
μαζί με τις πιθανές επιλογές.
Εμείς μέχρι στιγμής δεν έχουμε χρη-
181 # ερώτημα προς τον παίκτη σιμοποιήσει την choice() για να επι-
182 print("Ποιον πλανήτη δεν θα συναντήσουμε ", λέγουμε πλανήτες γιατί στις περισσό-
τερες περιπτώσεις δεν χρειαζόμαστε
183 "ταξιδεύοντας από τον πλανήτη ", μόνο έναν τυχαίο πλανήτη αλλά και
184 planets[start], " στον πλανήτη ", τη θέση του μέσα στη λίστα. Έτσι, επι-
185 planets[stop], ";", sep = "") λέγουμε πρώτα τυχαία τη θέση ενός
186 # πολλαπλή επιλογή πλανήτη και μετά αναφερόμαστε σε
αυτόν μέσω της θέσης του.
187 multipleChoice(4, between + [correct], correct)
Η λίστα των πιθανών απαντήσεων που δίνεται ως παράμετρος στην
συνάρτηση multipleChoice, είναι η συνένωση της λίστας between
ΔΙΑΠΛΑΝΗΤΙΚΟ ΚΟΥΙΖ 13

των ενδιάμεσων πλανητών και μιας λίστας που αποτελείται από ένα
μόνο στοιχείο: την σωστή απάντηση correct.
Στη συνέχεια θα προσθέσουμε την ερώτηση στο πρόγραμμά μας.
207 # εύρεση του "παράταιρου" πλανήτη
208 print("\nΕρώτηση 5:")
209 between(planets)
src/planets.9.py

Όλα Σε Τάξη
Ας τελειώσουμε μ’ ένα ερώτημα που θα εμφανίζει στον παίκτη ανακατε-
μένα τα ονόματα τεσσάρων διαδοχικών πλανητών και θα του ζητά να τα
βάλει σε σειρά, ξεκινώντας από τον κοντινότερο πλανήτη στον Ήλιο.
Η συνάρτηση που θα υλοποιήσουμε επιλέγει αρχικά τη θέση του
πρώτου πλανήτη της τετράδας και δημιουργεί μια νέα λίστα που
περιέχει τον πλανήτη αυτόν και τους τρεις που τον διαδέχονται.
188 def outOfOrder(planets):
189 ''' Καλεί τον παίκτη να βάλει στη σειρά
190 τα ονόματα 4 διαδοχικών πλανητών.
191 planets: λίστα με τα ονόματα όλων των πλανητών
192 '''
193 # τυχαία επιλογή θέσης αρχικού πλανήτη
194 nbPlanets = len(planets)
195 start = random.randint(0, nbPlanets - 5)
196 # δημιουργία λίστας 4 διαδοχικών πλανητών
197 fourPlanets = planets[start : start + 4]
Θα δημιουργήσουμε ένα αντίγραφο της λίστας των τεσσάρων πλα-
νητών, ώστε να τους ανακατέψουμε και να τους εμφανίσουμε στον
παίκτη με τυχαία σειρά. Δεν ανακατεύουμε απευθείας την αρχική
λίστα, τη χρειαζόμαστε για να υπολογίσουμε τη σωστή απάντηση.
Η συνάρτηση shuffle() της βιβλιο-
198 # αντίγραφο της λίστας των 4 διαδοχικών πλανητών
θήκης random δέχεται σαν παράμετρο
199 shuffled = fourPlanets.copy() μια λίστα και ανακατατάσσει τις τιμές
200 # ανακάτεμα των 4 πλανητών σε τυχαίες θέσεις της σε τυχαίες θέσεις.
201 random.shuffle(shuffled)
Τώρα μπορούμε να διατυπώσουμε την ερώτηση και να ζητήσουμε
την απάντηση του παίκτη. Τα ονόματα των πλανητών θα εμφανί-
ζονται αριθμημένα, ώστε ο παίκτης να γράφει τον αριθμό που αντι-
στοιχεί στον κάθε πλανήτη για να δώσει τη σωστή σειρά.
Η μέθοδος split() εφαρμόζεται σε
202 # ερώτηση στον παίκτη
αλφαριθμητικές τιμές, τις οποίες χω-
203 print("Βάλε τους πλανήτες με τη σωστή σειρά:") ρίζει σε επιμέρους τμήματα εκεί όπου
204 showMultiple(shuffled) υπάρχουν κενά και επιστρέφει μια λί-
205 print("\nΔώσε 4 αριθμούς με κενά ανάμεσά τους.") στα με τα τμήματα αυτά.
206 # η απάντηση "χωρίζεται" όπου υπάρχουν κενά Εδώ, η απάντηση του χρήστη θα έχει
207 # και τα συστατικά της τοποθετούνται σε μια λίστα τη μορφή "1 2 3 4" και η split()
208 answer = input().split() θα παράγει μια λίστα της μορφής
["1", "2", "3", "4"].
ΔΙΑΠΛΑΝΗΤΙΚΟ ΚΟΥΙΖ 14

Για να ελέγξουμε αν η απάντηση του παίκτη είναι σωστή, χρειάζε- fourPlanets shuffled
ται να κατασκευάσουμε μια λίστα αποτελούμενη από τους αριθμούς
των πλανητών με τη σειρά που πρέπει να τους δώσει ο παίκτης. Γη Άρης
0 0
Για να βρούμε τους αριθμούς που πρέπει να πληκτρολογήσει ο παί-
1 Άρης 1 Κρόνος
κτης πρέπει να διατρέξουμε την αρχική λίστα των πλανητών και
για κάθε πλανήτη να υπολογίσουμε τη θέση του στην ανακατεμένη 2 Δίας 2 Γη
λίστα. Για παράδειγμα: ποιός είναι ο πρώτος αριθμός που πρέπει
3 Κρόνος 3 Δίας
να πληκτρολογήσει ο παίκτης; Είναι ο αριθμός που έχει ο πρώτος
πλανήτης της αρχικής λίστας μέσα στην ανακατεμένη λίστα.
Σχήμα 5.9: Ένα παράδειγμα. Η Γη εί-
209 # λίστα που θα περιέχει την σωστή απάντηση ναι πρώτη στην αρχική λίστα και
τρίτη στην ανακατεμένη λίστα. Άρα ο
210 correct = []
πρώτος αριθμός που πρέπει να πλη-
211 # για κάθε έναν από τους 4 διαδοχικούς πλανήτες κτρολογήσει ο χρήστης είναι το 3.
212 for planet in fourPlanets:
Η μέθοδος index() εφαρμόζεται σε
213 # σειρά του πλανήτη στην ανακατεμένη λίστα μια λίστα. Δέχεται σαν παράμετρο
214 position = shuffled.index(planet) + 1 ένα στοιχείο της λίστας και επιστρέ-
215 # προσθήκη σωστής σειράς στην απάντηση φει τη θέση του σε αυτή. Αν το στοι-
216 correct.append(str(position)) χείο δεν υπάρχει στη λίστα τότε προ-
κύπτει σφάλμα.
Ας ελέγξουμε πλέον αν η απάντηση του παίκτη είναι σωστή. Η συνάρτηση str() μετατρέπει μια
217 # έλεγχος απάντησης τιμή σε αλφαριθμητική. Εδώ είναι
απαραίτητο να μετατρέψουμε τις θέ-
218 if answer == correct: σεις των πλανητών σε αλφαριθμητι-
219 print("Μπράβο, η σειρά είναι σωστή.") κές για να συγκριθούν με τις αντίστοι-
220 else: χες αλφαριθμητικές τιμές που θα δια-
221 print("Η σωστή σειρά είναι:") βαστούν από το πληκτρολόγιο.
222 print(correct) Ο τελεστής == μπορεί να χρησιμοποι-
ηθεί και για να συγκριθούν δύο λίστες,
Ολοκληρώνουμε τα ερωτήματα που θα εμφανίσουμε στον παίκτη, στοιχείο προς στοιχείο.
καλώντας την συνάρτηση απ’ το κύριο πρόγραμμα.
245 # εύρεση της σωστής σειράς των πλανητών
246 print("\nΕρώτηση 6:")
247 outOfOrder(planets)
src/planets.10.py

Τροποποιήσεις – Επεκτάσεις
Στο κεφάλαιο αυτό μάθαμε πολλά για τις λίστες και τους διάφο-
ρους τρόπους με τους οποίους μπορούμε να τις επεξεργαστούμε στην
Python. Για να είναι ευκολότερο για εσάς να ανατρέχετε σε όλα
αυτά καθώς λύνετε τις ασκήσεις, τα συγκεντρώσαμε όλα (και με-
ρικά ακόμα) σ’ ένα “σκονάκι”. pythonies.mysch.gr/cslists.pdf

5.1 Υλοποιήστε τη συνάρτηση addPluto() έτσι ώστε να επιστρέφει την τιμή


True όταν η απάντηση του χρήστη περιέχεται σε μια λίστα πιθανών
καταφατικών απαντήσεων.
Μπορείτε να εφαρμόσετε τη μέθοδο lower() στην απάντηση του χρήστη,
οπότε η λίστα των πιθανών καταφατικών απαντήσεων θα είναι μικρότερη
αφού θα πρέπει να περιέχει μόνο απαντήσεις με πεζά γράμματα.
ΔΙΑΠΛΑΝΗΤΙΚΟ ΚΟΥΙΖ 15

5.2 Υλοποιήστε τη συνάρτηση select(), έτσι ώστε η λίστα των πιθανών


απαντήσεων να κατασκευάζεται επαναληπτικά ως εξής: σε κάθε βήμα
θα επιλέγεται τυχαία μια απάντηση και θα προστίθεται στη λίστα με τις
πιθανές απαντήσεις μέχρι να συμπληρωθεί ο απαραίτητος αριθμός. Θα
πρέπει να εξασφαλίζεται ότι η σωστή απάντηση περιλαμβάνεται στην
τελική λίστα και να γίνεται ο απαραίτητος έλεγχος, ώστε να μην προ-
στίθεται πολλές φορές ο ίδιος πλανήτης στις πιθανές απαντήσεις.
Διακρίνετε κάποιο μειονέκτημα σε μια τέτοια υλοποίηση;

5.3 Υλοποιήστε τη συνάρτηση select(), έτσι ώστε να ανακατεύεται ένα


αντίγραφο της αρχικής λίστας των απαντήσεων και μετά να επιλέγεται
από αυτό ένα τμήμα με τη λειτουργία του τεμαχισμού, ως λίστα πιθα-
νών απαντήσεων. Θα πρέπει να εξασφαλίζεται ότι η σωστή απάντηση
περιλαμβάνεται στην τελική λίστα.
Διακρίνετε κάποιο μειονέκτημα σε μια τέτοια υλοποίηση;

5.4 Μπορείτε να φτιάξετε ένα παιχνίδι με ερωτήσεις πολλαπλής επιλογής


που θα αφορούν τις ημέρες της εβδομάδας, αντί για τους πλανήτες, και
θα απευθύνεται σε παιδιά πρώτης δημοτικού.
Mερικές ενδεικτικές ερωτήσεις: Ποιά από τις παρακάτω μέρες δεν πηγαί-
νουμε σχολείο; Ποια μέρα είναι πριν ή μετά τη x; Αν σήμερα η μέρα είναι x,
ποια ημέρα εννοούμε όταν λέμε “αύριο”, “μεθαύριο”, “χθες” ή “προχθές”; Αν
σήμερα η μέρα είναι x, τι μέρα θα είναι σε n μέρες; Αν σήμερα η μέρα είναι
y, πόσες ημέρες έχουν περάσει από τη x; Βάλτε στη σειρά τις παρακάτω
ημέρες της εβδομάδας.
Εννοείται πως τα στοιχεία των ερωτήσεων που συμβολίζονται με x, y και
n, όπως και οι πιθανές απαντήσεις, θα επιλέγονται κάθε φορά τυχαία.

5.5 Μπορείτε να φτιάξετε ένα παιχνίδι με παρόμοιες ερωτήσεις πολλαπλής


επιλογής που θα αφορούν τους μήνες και τις εποχές, αντί για τους πλα-
νήτες, και θα απευθύνεται σε παιδιά πρώτης και δευτέρας δημοτικού.
Mερικές ενδεικτικές ερωτήσεις: Ποιός είναι ο n-οστός μήνας του χρόνου;
Ποιος αριθμός αντιστοιχεί στον μήνα x; Ποιος μήνας έρχεται πριν ή μετά
τον μήνα x; Ποιος μήνας είναι πιο κοντά στην αρχή ή στο τέλος της χρο-
νιάς; Σε ποια εποχή ανήκει ο μήνας x; Ποιος μήνας δεν ανήκει στην εποχή
y; Ποια εποχή δεν “εκπροσωπείται” στους παρακάτω μήνες;
Εννοείται πως τα στοιχεία των ερωτήσεων που συμβολίζονται με x, y και
n, όπως και οι πιθανές απαντήσεις, θα επιλέγονται κάθε φορά τυχαία.

5.6 Να τροποποιήσετε το πρόγραμμα που παίζει Πέτρα – Ψαλίδι – Χαρτί


έτσι ώστε να χρησιμοποιεί μια λίστα που περιέχει τις λέξεις "Πέτρα",
"Ψαλίδι" και "Χαρτί". Έτσι, οι (ακέραιες) επιλογές του παίκτη και του
προγράμματος θα αντιστοιχίζονται άμεσα σε μια από αυτές τις λέξεις,
χωρίς την ανάγκη να χρησιμοποιηθεί η if.
ΔΙΑΠΛΑΝΗΤΙΚΟ ΚΟΥΙΖ 16

Ασκήσεις
5.7 Τα μπισκότα τύχης (fortune cookies) είναι μπισκότα που στο εσωτερικό
τους περιέχουν μηνύματα με προβλέψεις γι’ αυτόν που τα ανοίγει. Υλο-
ποιήστε ένα πρόγραμμα που θα καλεί τον χρήστη να ανοίξει ένα τυχερό
μπισκότο και στη συνέχεια θα του εμφανίζει μια τυχαία πρόβλεψη.
Μερικές ιδέες για τις προφητείες των μπισκότων: Η τύχη σου κρύβεται σε
άλλο μπισκότο. Ο καλύτερος τρόπος να παραμείνεις υγιής είναι να συ-
νεχίσεις να τρως τυχερά μπισκότα. Αποδέξου ότι κάποιες μέρες είσαι το
περιστέρι και κάποιες άλλες το άγαλμα. Μαθαίνεις από τα λάθη σου…
Σήμερα είναι μια μέρα που θα διδαχθείς πολλά. Η σκληρή δουλειά θα σου
ανταποδώσει στο μέλλον. Η τεμπελιά θα σου ανταποδώσει άμεσα… Όλοι
συμφωνούν: Είσαι ο καλύτερος!

5.8 Στο παιχνίδι της αντιστροφής, εμφανίζεται στον παίκτη μια λίστα με Η μέθοδος reverse() εφαρμόζεται σε
αριθμούς κι εκείνος καλείται να τους αναδιατάξει σε αύξουσα σειρά. Το μια λίστα κι αντιστρέφει τα στοι-
χεία της. Για παράδειγμα, η εντολή
μόνο πράγμα που καθορίζει ο παίκτης σε κάθε κίνησή του είναι πόσοι L.reverse() αντιστρέφει τη λίστα L.
αριθμοί θ’ αντιστραφούν (ξεκινώντας από τ’ αριστερά).
Ο τεμαχισμός μπορεί επίσης να χρη-
Για παράδειγμα, αν οι αριθμοί είναι: σιμοποιηθεί για την αντιστροφή μιας
λίστας. Για παράδειγμα, η έκφραση
2 3 4 5 1 6 7 8 9 L[::-1] δημιουργεί μια νέα λίστα,
που περιέχει όλα τα στοιχεία της L με
και ο παίκτης επιλέξει ν’ αντιστρέψει τέσσερις αριθμούς, τότε οι αριθμοί την αντίστροφη σειρά.
θα γίνουν:
Εδώ χρειάζεται ν’ αντιστρέψετε μόνο
5 4 3 2 1 6 7 8 9 ένα τμήμα της λίστας, οπότε θα χρεια-
στεί πρώτα να την τεμαχίσετε και, μετά
Αν στη συνέχεια ο παίκτης αντιστρέψει πέντε αριθμούς, κερδίζει! την αντιστροφή, να συνενώσετε τα
τμήματά της.
1 2 3 4 5 6 7 8 9
Να κατασκευάσετε πρόγραμμα που εμφανίζει στον παίκτη μια λίστα με
τους αριθμούς από το 1 μέχρι το 9 ανακατεμένους και στη συνέχεια ζητά
από τον παίκτη επαναληπτικά πόσους αριθμούς να αντιστρέψει. Το παι-
χνίδι τελειώνει όταν ο παίκτης καταφέρει να τοποθετήσει τους αριθμούς
στη σωστή σειρά ή όταν παραιτηθεί, απαντώντας ότι επιθυμεί να αντι-
στρέψει μηδέν αριθμούς. Αν ο παίκτης καταφέρει να ολοκληρώσει το παι-
χνίδι, το πρόγραμμα θα πρέπει να εμφανίζει πόσες κινήσεις χρειάστηκαν.
Όταν ολοκληρώσετε το πρόγραμμά σας, προσπαθήστε να το τροποποιή-
σετε έτσι ώστε οι κινήσεις να μην επιλέγονται από το χρήστη, αλλά από
το πρόγραμμα, το οποίο πια θα παίζει μόνο του.
5.9 Ο Ηλεκτρικός της Αθήνας εγκαινιάστηκε το 1869 και συνέδεε τον Πει-
ραιά με το Θησείο. Μετά από 88 χρόνια η διαδρομή επεκτάθηκε μέχρι την
Κηφισιά. Σήμερα αριθμεί 24 σταθμούς.
Υλοποιήστε ένα πρόγραμμα που θα βοηθά το χρήστη να μετακινηθεί με Βρείτε τη λίστα με τους σταθμούς
τον Ηλεκτρικό. Ο χρήστης θα δίνει το όνομα του σταθμού από τον οποίο στο αρχείο pythonies.mysch.gr/
src/metro.py. Αν το πρόγραμμά
θ’ αναχωρήσει και το όνομα του σταθμού στον οποίο θέλει να φτάσει
σας βρίσκεται στον ίδιο κατάλογο με
και το πρόγραμμα θα του εμφανίζει την κατεύθυνση που θα ακολουθήσει αυτό το αρχείο μπορείτε να γράψετε
(προς Πειραιά ή προς Κηφισιά) και τα ονόματα όλων των ενδιάμεσων from metro import stations και
σταθμών της διαδρομής. από εκεί και πέρα θα μπορείτε να
χρησιμοποιήσετε τη λίστα stations
με τους 24 σταθμούς.
ΔΙΑΠΛΑΝΗΤΙΚΟ ΚΟΥΙΖ 17

Ένα παράδειγμα αλληλεπίδρασης με το πρόγραμμα:


Δώστε σταθμό αφετηρίας
ΠΕΤΡΑΛΩΝΑ
Δώστε σταθμό προορισμού
ΦΑΛΗΡΟ
Σταθμοί από ΠΕΤΡΑΛΩΝΑ προς ΦΑΛΗΡΟ με κατεύθυνση ΠΕΙΡΑΙΑΣ
ΤΑΥΡΟΣ
ΚΑΛΛΙΘΕΑ
ΜΟΣΧΑΤΟ
Μετά την εμφάνιση των πληροφοριών, το πρόγραμμα θα ρωτάει το χρή-
στη αν επιθυμεί πληροφορίες για κάποια άλλη διαδρομή και θα επανα-
λαμβάνει τη διαδικασία σε περίπτωση καταφατικής απάντησης.

5.10 Το τρίγωνο του Pascal είναι ένας τριγωνικός μαθηματικός πίνακας με


πολύ ενδιαφέρουσες ιδιότητες. Για να το κατασκευάσουμε, τοποθετούμε
αρχικά στην κορυφή του τριγώνου, δηλαδή στη γραμμή 0, τον αριθμό 1.
Kάθε αριθμός σε κάθε επόμενη γραμμή προκύπτει ως άθροισμα των δύο
αριθμών που βρίσκονται από πάνω του, εκτός από τον πρώτο και τον
τελευταίο αριθμό κάθε γραμμής που είναι πάντα το 1.

n = 0 1
n = 1 1 1
n = 2 1 2 1
n = 3 1 3 3 1
n = 4 1 4 6 4 1
n = 5 1 5 10 10 5 1
n = 6 1 6 15 20 15 6 1
Σχήμα 5.10: Οι 7 πρώτες γραμμές του τριγώνου του Pascal.

Από το τρίγωνο του Pascal μπορούμε να εξάγουμε τους τριγωνικούς αριθ-


μούς και τους αριθμούς Fibonacci, να υπολογίσουμε το ανάπτυγμα πο-
λυωνύμων ακόμα και να κατασκευάσουμε ένα φράκταλ που ονομάζεται
τρίγωνο Sierpinski.
Μια ιδιότητα του τριγώνου που θα χρησιμοποιήσουμε εμείς είναι η εξής:
ο αριθμός που βρίσκεται στη γραμμή n και στη στήλη y αντιστοιχεί στο
πλήθος των διαφορετικών τρόπων με τους οποίους μπορούμε να φέρουμε
y φορές κορώνα αν στρίψουμε ένα νόμισμα n φορές (θυμηθείτε ότι οι
γραμμές και οι στήλες αριθμούνται από το 0). Διαιρώντας αυτόν τον
αριθμό με το άθροισμα των αριθμών της γραμμής n, το οποίο είναι 2n ,
υπολογίζουμε την πιθανότητα να φέρουμε y φορές κορώνα αν στρίψουμε
ένα νόμισμα n φορές.
Να υλοποιήσετε ένα πρόγραμμα το οποίο διαβάζει το n από το χρήστη,
κατασκευάζει το τρίγωνο του Pascal μέχρι και τη γραμμή n και εμφανίζει,
για κάθε y, την πιθανότητα να φέρουμε y φορές κορώνα αν στρίψουμε
ένα νόμισμα n φορές.

5.11 Στο τυχερό παιχνίδι Λοττο, ο παίκτης επιλέγει έξι αριθμούς από το 1
μέχρι και το 49. Στη συνέχεια κληρώνονται έξι αριθμοί με τυχαίο τρόπο.
ΔΙΑΠΛΑΝΗΤΙΚΟ ΚΟΥΙΖ 18

Ανάλογα με το πλήθος των αριθμών που κατάφερε να μαντέψει ο παίκτης,


έχει και τα αντίστοιχα κέρδη.
Να υλοποιήσετε πρόγραμμα που θα ζητάει από τον χρήστη τους έξι αριθ-
μούς της επιλογής του. Στη συνέχεια θα παράγει τους έξι τυχερούς αριθ-
μούς που κληρώνονται. Θα πρέπει να εξασφαλίζεται ότι δεν εμφανίζεται
πολλές φορές ο ίδιος αριθμός είτε στην εξάδα των τυχερών αριθμών είτε
στην εξάδα των αριθμών που επιλέγει ο παίκτης. Το πρόγραμμα θα εμ- Η συνάρτηση sorted() δέχεται σαν
φανίζει στον παίκτη τους αριθμούς που επέλεξε, τους τυχερούς αριθμούς παράμετρο μια λίστα και επιστρέ-
φει μια νέα ταξινομημένη λίστα, χω-
και πόσους από τους τυχερούς αριθμούς πέτυχε σωστά. ρίς να πειράζει την αρχική. Η μέθοδος
Καλό είναι να υλοποιήσετε τις επιμέρους λειτουργίες του προγράμματος sort() εφαρμόζεται σε μια λίστα και
με συναρτήσεις. Θα μπορούσατε να υλοποιήσετε τις εξής: ταξινομεί τα στοιχεία της. Με τα πα-
ραπάνω θα μπορέσετε να εμφανίζετε
• inputNumbers() που διαβάζει από το χρήστη 6 αριθμούς, εξασφα- στο χρήστη τις εξάδες των αριθμών
λίζοντας ότι βρίσκονται μεταξύ 1 και 49 και δεν επαναλαμβάνονται, ταξινομημένες, όπως συνηθίζεται.
και τους επιστρέφει σε μια λίστα.
• generateNumbers() που δημιουργεί 6 τυχαίους αριθμούς μεταξύ 1
και 49, εξασφαλίζοντας ότι δεν επαναλαμβάνονται, και τους επι-
στρέφει σε μια λίστα.
• compare() που δέχεται σαν παραμέτρους δύο λίστες και επιστρέφει
μια λίστα με τα στοιχεία που βρίσκονται και στις δύο. Εναλλακτικά,
θα μπορούσε να επιστρέφει απλά το πλήθος αυτών των στοιχείων.
5.12 Το παιχνίδι Blackjack είναι ένα παιχνίδι με χαρτιά που παίζεται από δύο
παίκτες. Ένας από τους δύο κάνει τη “μάνα” και είναι εκείνος που διαχει-
ρίζεται την τράπουλα. Η τράπουλα περιέχει δεκατρία χαρτιά που επα- Μπορείτε να αναπαραστήσετε την
ναλαμβάνονται τέσσερις φορές (σύνολο 52): τα χαρτιά από 1 έως 10 και τράπουλα σαν λίστα με 52 στοιχεία.
Για να κατασκευάσετε αυτή τη λίστα,
τις φιγούρες του Βαλέ, της Ντάμας και του Ρήγα. Η αξία του Βαλέ, της μπορείτε να ξεκινήσετε με μια κενή λί-
Ντάμας και του Ρήγα είναι 10 πόντοι, ενώ η αξία του Άσσου μπορεί να στα, στην οποία θα προσθέτετε επα-
υπολογιστεί είτε ως 1 είτε ως 11, ανάλογα με το συμφέρον του παίκτη. ναληπτικά τα στοιχεία με την μέθοδο
append(). Εναλλακτικά, με την έκ-
Αρχικά, η τράπουλα ανακατεύεται και η “μάνα” με τον παίκτη παίρ- φραση 52 * [None], μπορείτε να ξε-
νουν από δύο χαρτιά ο καθένας: τα χαρτιά του παίκτη είναι “ανοικτά”, κινήσετε με μια λίστα 52 στοιχείων
δηλαδή φαίνεται η ένδειξή τους, ενώ η “μάνα” έχει ένα χαρτί “ανοικτό” χωρίς τιμή (αυτό είναι το None) και
να προσδιορίσετε επαναληπτικά την
και κρατά το άλλο “κλειστό”. Η “μάνα” ρωτάει τον παίκτη αν επιθυμεί κι
τιμή κάθε στοιχείου. Μπορείτε ακόμα
άλλο χαρτί, διαδικασία που επαναλαμβάνεται όσο ο παίκτης απαντά κα- και να κατασκευάσετε μια λίστα L,
ταφατικά και η συνολική αξία των χαρτιών του είναι μικρότερη από 21. που θα περιέχει μόνο τα 13 μονα-
Στη συνέχεια, εφόσον το άθροισμα των χαρτιών του παίκτη δεν ξεπερνά δικά χαρτιά της τράπουλας, και μετά
να κατασκευάσετε ολόκληρη την τρά-
το 21, η μάνα αποκαλύπτει το “κλειστό” της χαρτί και επαναλαμβάνει
πουλα με την έκφραση L * 4.
τη διαδικασία για τον εαυτό της. Αν κάποιος από τους δύο παίκτες ξεπε-
ράσει το 21 τότε “καίγεται” και χάνει αυτομάτως. Σε διαφορετική περί-
πτωση, νικητής είναι εκείνος του οποίου τα χαρτιά έχουν το μεγαλύτερο
άθροισμα, με τη “μάνα” να θεωρείται νικήτρια σε περίπτωση ισοβαθμίας.
Υλοποιήστε ένα πρόγραμμα το οποίο θα έχει το ρόλο της “μάνας” και θα
παίζει Blackjack με αντίπαλο το χρήστη.
Υπόδειξη: Όσους Άσσους κι αν έχει ένας παίκτης, μόνο ένας από αυτούς
μπορεί να μετρήσει ως 11. Επομένως, το πρόγραμμά σας θα πρέπει να ελέγ-
χει το πολύ δύο αθροίσματα, το κανονικό άθροισμα των χαρτιών και, στην
περίπτωση που υπάρχει τουλάχιστον ένας Άσσος, το λεγόμενο “μαλακό”
άθροισμα, στο οποίο ο Άσσος μετράει ως 11.
ΔΙΑΠΛΑΝΗΤΙΚΟ ΚΟΥΙΖ 19

Δομες Δεδομενων και Λιστες Οι λίστες, δομές απλές κι ευέλικτες, έχουν βαρύνουσα σημασία στην ιστορία
της Πληροφορικής. Η δεύτερη γλώσσα προγραμματισμού που αναπτύχθηκε, η LISP του John McCarthy, βασί-
ζεται στις λίστες. Η Logo, με την σειρά της, μια ιστορική εκπαιδευτική γλώσσα προγραμματισμού, βασίζεται
κυρίως στη LISP.
Οι λίστες είναι ένα παράδειγμα οργάνωσης των δεδομένων μας, έτσι ώστε να αποτελούν μια ενιαία συλλογή
με συγκεκριμένη δομή. Υπάρχουν αναρίθμητοι τρόποι να οργανώσουμε τα δεδομένα μας, γι’ αυτό και υπάρχουν
και πάρα πολλές δομές δεδομένων. Ακόμα και οι πιο θεμελιώδεις έχουν εξωτικά ονόματα όπως στοίβες, ουρές,
δέντρα, σωροί ή γράφοι. Ο τρόπος οργάνωσης καθορίζει πόσο εύκολο είναι να επεξεργαστούμε τα δεδομένα
μας με συγκεκριμένο τρόπο. Γι’ αυτό και επιλέγουμε τη δομή που θα χρησιμοποιήσουμε κάθε φορά ανάλογα
με τον συγκεκριμένο τρόπο που θέλουμε να επεξεργαστούμε τα δεδομένα μας, στα πλαίσια ενός συγκεκριμένου
προβλήματος.
Μέχρι να αντιμετωπίσει κανείς στοιχειωδώς περίπλοκα προβλήματα είναι δύσκολο να διακρίνει γιατί χρειάζε-
ται να οργανωθούν τα δεδομένα – οι μεμονωμένες μεταβλητές φαίνονται υπεραρκετές, αλλά δεν είναι. Αν είστε
άνθρωποι με περιπετειώδες πνεύμα, μπορείτε να επιχειρήσετε να υλοποιήσετε το παιχνίδι αυτού του κεφαλαίου
χωρίς να χρησιμοποιήσετε λίστες.

Αριθμηση απο το Μηδεν Υπάρχουν δύο ειδών άνθρωποι στον κόσμο: (1) αυτοί που ξεκινούν την αρίθμηση
από το ένα και (1) αυτοί που ξεκινούν την αρίθμηση από το μηδέν. Αν πιάσατε το αστειάκι, έχετε καταλάβει αυτό
το λεπτό σημείο του κεφαλαίου. Η χρήση της αρίθμησης από το μηδέν ξεκίνησε τη δεκαετία του ’60 από ανάγκη,
για καθαρά τεχνικούς λόγους και είναι αλήθεια ότι σε εκείνους που την συναντούν για πρώτη φορά φαίνεται
από άβολη έως παρανοϊκή. Ωστόσο, έχει και σημαντικά πλεονεκτήματα. Ο ιδιόρυθμος Edsger Dijkstra, ένας
από τους ανθρώπους που συνέβαλαν καθοριστικά στην καθιέρωση της Πληροφορικής ως επιστήμη, έγραψε το
1982 τρεις ολόκληρες (χειρόγραφες) σελίδες με τίτλο “Γιατί η αρίθμηση πρέπει να ξεκινά από το μηδέν”. Τώρα, η
αρίθμηση από το μηδέν είναι πια από τα πολύ χαρακτηριστικά ιδιώματα της Πληροφορικής. Στο παιδικό βιβλίο
“Lauren Ipsum” του Carlos Bueno, η αρίθμηση των κεφαλαίων ξεκινά από το μηδέν και η ηρωίδα, καθώς αρχίζει
την περιπέτειά της, συναντά μια πινακίδα όπου το πρώτο μίλι της διαδρομής είναι φυσικά… το μηδενικό.

Σχήμα 5.11: Από το Κεφάλαιο 0 του


Lauren Ipsum.
Τρίλιζα 5
Στο κεφάλαιο αυτό θα υλοποιήσουμε το γνωστό παιχνίδι της τρίλιζας. Θα 23 Νοεμβρίου 2016
αναπτύξουμε τον απαραίτητο κώδικα για ένα παιχνίδι δύο παικτών και 15:21
μετά θα προσθέσουμε τις απαραίτητες πινελιές ώστε τον ρόλο του ενός
παίκτη να τον αναλαμβάνει το ίδιο το πρόγραμμα. Προγραμματιστικά, θα
εξασκηθούμε σε όλες τις έννοιες που έχουμε ήδη συναντήσει και θα δούμε
πως μπορούμε να χρησιμοποιήσουμε λίστες και πλειάδες για την αναπα-
ράσταση των δεδομένων μας. Η τρόπος που επιλέγουμε να αναπαραστή-
σουμε τα δεδομένα είναι αλληλένδετος με τον τρόπο που τα επεξεργαζό-
μαστε και επηρρεάζει άμεσα τη διαδικασία επίλυσης ενός προβλήματος.
Έννοιες: υποπρογράμματα, αναπαραστάσεις, λίστες, πλειάδες

Το 1952, o βρετανός Sandy Douglas έγραψε, στα πλαίσια του δι-


δακτορικού του, ένα πρόγραμμα για τον υπολογιστή EDSAC που
έπαιζε τρίλιζα. Αυτό το πρόγραμμα θεωρείται ένα από τα πρώτα βι-
ντεοπαιχνίδια. Ο πίνακας της τρίλιζας προβαλλόνταν σε μια πρω-
τόγονη οθόνη και ο χρήστης επέλεγε την κίνησή του χρησιμοποιώ-
ντας το περιστρεφόμενο καντράν ενός τηλεφώνου.
Ένα τέτοιο πρόγραμμα για την τρίλιζα δεν είναι ιδιαίτερα δύσκολο
να γραφτεί, η τρίλιζα είναι απλό παιχνίδι. Μάλιστα θα μπορούσε να
χαρακτηριστεί ακόμα και βαρετό: όσο καλά και να παίξει κανείς, δεν
μπορεί να κερδίσει τον αντίπαλό του παρά μόνο αν κάνει λάθη. Το
«φυσιολογικό» αποτέλεσμα είναι η ισοπαλία. Κι όμως, οι άνθρωποι
παίζουν τρίλιζα μ’ ενθουσιασμό. Μάλιστα, όσο μεγαλύτερος είναι ο
ενθουσιασμός, τόσο ευκολότερα χάνουν…

Αναπαραστάσεις
Πριν ξεκινήσουμε να γράψουμε έστω και μια γραμμή του προγράμ-
ματος, πρέπει να πάρουμε μια σημαντική απόφαση: ποια αναπαρά-
σταση θα χρησιμοποιήσουμε για την τρίλιζα; Δηλαδή ποιος θα εί-
ναι ο τρόπος με τον οποίο το πρόγραμμά μας θα αποθηκεύει και θα
διαχειρίζεται εσωτερικά τα περιεχόμενα των εννέα τετραγώνων του
παιχνιδιού; Η απόφαση αυτή είναι κομβική και θα επηρρεάσει σημα-
ντικά τη μορφή του προγράμματος που θα γράψουμε στη συνέχεια.

1
ΤΡΙΛΙΖΑ 2

Δεν είναι βολικό ν’ αποθηκεύσουμε το περιεχόμενο των εννέα τε-


τραγώνων σε εννέα ξεχωριστές μεταβλητές. Θα καταλήξουμε με ένα
δυσνόητο, εκτεταμένο πρόγραμμα με επαναλαμβανόμενα τμήματα.
Αντιθέτως, θα θέλαμε τα εννέα τετράγωνα να αναπαρασταθούν ως
μια ενιαία, οργανωμένη συλλογή δεδομένων.
Στο δικό μας πρόγραμμα θα χρησιμοποιήσουμε για τον σκοπό αυτό
μια λίστα, δηλαδή μια συλλογή στοιχείων που είναι οργανωμένα
ακολουθιακά, το ένα μετά το άλλο. Σε μια λίστα, τα στοιχεία είναι
αριθμημένα για να μπορούμε μέσω αυτής της αρίθμησης να έχουμε
πρόσβαση στο περιεχόμενό τους.
Ας εξετάσουμε ένα παράδειγμα, ένα συγκεκριμένο στιγμιότυπο του
πίνακα της τρίλιζας:

Η σχετική λίστα θα αποτελείται από εννέα στοιχεία, σε κάθε ένα


από τα οποία θα αποθηκεύεται το περιεχόμενο του αντίστοιχου τε-
τραγώνου της τρίλιζας. Μια απεικόνιση της λίστας φαίνεται στο
σχήμα 5.1 που ακολουθεί.
τετράγωνα της τετράγωνα της τετράγωνα της Στην Python, όπως και σε πολλές άλ-
1ης γραμμής 2ης γραμμής 3ης γραμμής λες γλώσσες, κάθε είδους αρίθμηση
ξεκινά από το 0, όχι από το 1. Έτσι,
το πρώτο στοιχείο αυτής της λίστας
που αναπαριστά τον πίνακα του παι-
χνιδιού βρίσκεται στη θέση 0 και το
0 1 2 3 4 5 6 7 8 ένατο στη θέση 8.

Σχήμα 5.1: Παράδειγμα αναπαράστασης ενός συγκεκριμένου στιγμιοτύπου του Γενικά, τα στοιχεία μιας λίστας μπο-
παιχνιδιού με τη χρήση λίστας. Σε κάθε στοιχείο της λίστας αποθηκεύεται το περιε- ρεί να είναι ο,τιδήποτε, ακόμα και άλ-
χόμενο ενός τετραγώνου της τρίλιζας. Η συγκεκριμένη αντιστοιχία μεταξύ τετρα- λες λίστες.
γώνων και στοιχείων της λίστας είναι απλά μία από τις πολλές που θα μπορούσαν Στη συγκεκριμένη περίπτωση, όλα τα
να χρησιμοποιηθούν. στοιχεία της λίστας θα είναι αλφαριθ-
μητικά και κάθε ένα από αυτά θα έχει
Η αναπαράσταση αυτή δεν είναι η μοναδική, υπάρχουν πολλές εναλ-
τιμή είτε "X", είτε "O", είτε " " (κενό).
λακτικές. Όμως δεν είναι εύκολο να γνωρίζουμε εκ των προτέρων
ποια από τις πιθανές αναπαραστάσεις θ’ αποδειχθεί βολικότερη.
Ουσιαστικά, η επιλογή της κατάλληλης αναπαράστασης εξαρτάται
από τον συγκεκριμένο τρόπο που το πρόγραμμά μας θα επεξεργά-
ζεται τα δεδομένα. Εφόσον ακόμα δεν έχουμε γράψει το πρόγραμμα,
μόνο η εμπειρία και η διαίσθηση μπορεί να μας καθοδηγήσει.

Κάτι Να “Μεταφράζει”
Εντάξει, επιλέξαμε την αναπαράσταση. Αλλά, όπως και να προχωρή-
σουμε, ο χρήστης θα πρέπει να βλέπει μια τρίλιζα για να παίζει, όχι εννέα
τετράγωνα στη σειρά.
ΤΡΙΛΙΖΑ 3

Ο τρόπος που το πρόγραμμα διαχειρίζεται “εσωτερικά” την αναπα-


ράσταση της τρίλιζας δεν ενδιαφέρει το χρήστη. Στην οθόνη θα πρέ-
πει να εμφανίζεται μια αναπαράσταση που να είναι γνώριμη στους
παίκτες, δηλαδή ο κλασικός 3 × 3 πίνακας του παιχνιδιού.
Θα ξεκινήσουμε λοιπόν κατασκευάζοντας ένα υποπρόγραμμα που
δέχεται σαν παράμετρο την εσωτερική αναπαράσταση board της
τρίλιζας (εδώ πρόκειται για μια λίστα με εννέα στοιχεία) και την
εμφανίζει στην οθόνη με τρόπο φιλικό προς το χρήστη.
Μπορούμε ν’ αναφερθούμε στα στοι-
1 def print3x3(board, trailing = True):
χεία μιας λίστας με βάση τη θέση
2 """ Εμφανίζει σε διάταξη 3x3 τα περιεχόμενα τους σε αυτή. Η αρίθμηση των θέ-
3 μιας λίστας με 9 (τουλάχιστον) στοιχεία. σεων ξεκινά από το 0. Έτσι, εδώ το
4 board: Λίστα που θα εμφανιστεί σε διάταξη 3χ3 πρώτο στοιχείο της λίστας board εί-
5 """ ναι το board[0], το δεύτερο είναι το
board[1], κ.ο.κ.
6 print(" ", board[0], "|", board[1], "|", board[2])
7 print(" ---+---+---") Η παράμετρος trailing καθορίζει αν
θα εμφανιστεί μια κενή γραμμή μετά
8 print(" ", board[3], "|", board[4], "|", board[5]) τον 3 × 3 πίνακα και έχει σαν προκαθο-
9 print(" ---+---+---") ρισμένη τιμή την True. Αυτό σημαίνει
10 print(" ", board[6], "|", board[7], "|", board[8]) ότι υπάρχει η δυνατότητα να μην κα-
11 if trailing: θοριστεί η τιμή της παραμέτρου κατά
την κλήση της συνάρτησης. Στην πε-
12 print() ρίπτωση αυτή, η παράμετρος θα πάρει
Αυτή η αντιστοιχία ανάμεσα στα τετράγωνα και τις θέσεις της λί- την προκαθορισμένη της τιμή.
στας, όπως εξάλλου και η ίδια η αναπαράσταση με λίστα, αφορά
μόνο εμάς ως προγραμματιστές και ο χρήστης ούτε τη γνωρίζει, ούτε
τον αφορά. Όταν το πρόγραμμά μας θα καλεί την print3x3, ο χρή-
στης θα βλέπει απλά στην οθόνη τα τετράγωνα της τρίλιζας.

Quick and Dirty


Για αρχή, νομίζω ότι μπορώ να φτιάξω κάτι στα γρήγορα που να λειτουρ-
γεί. Δεν θα είναι τέλειο και στην πορεία θ’ αλλάξω αρκετά πράγματα,
όμως έτσι θα διαπιστώσω ποια σημεία παρουσιάζουν δυσκολίες.
Αρχικές τιμές

Στο κύριο πρόγραμμα, η λίστα στην οποία θ’ αποθηκεύουμε την τρέ-


χουσα κατάσταση της τρίλιζας θα ονομάζεται board. Αρχικά, πριν
ξεκινήσει το παιχνίδι, η τρίλιζα θα περιέχει εννέα κενά τετράγωνα.
Όταν “πολλαπλασιάζουμε” μια λίστα
13 # η αναπαράσταση της τρίλιζας:
με τον τελεστή * κατασκευάζουμε μια
14 # μια λίστα με 9 χαρακτήρες (" ", "Χ" ή "Ο") νέα λίστα που περιέχει πολλές φορές
15 # αρχικά, όλα τα τετράγωνα είναι κενά τα στοιχεία της αρχικής.
16 board = 9 * [" "] Εδώ, η λίστα board προκύπτει πολλα-
Όπως και στο Παιχνίδι της Αφαίρεσης, που ήταν επίσης παιχνίδι πλασιάζοντας επί 9 τη λίστα [" "],
που αποτελείται από ένα στοιχείο.
δύο παικτών, θα χρησιμοποιήσουμε μια μεταβλητή player, η οποία
σε κάθε γύρο θα παίρνει εναλλάξ την τιμή "X" ή "O", υποδεικνύοντας
με αυτόν τον τρόπο ποιος έχει σειρά να παίξει σε κάθε γύρο. Κατά
σύμβαση, πρώτος παίζει ο παίκτης με τα X, οπότε πριν ξεκινήσει το
παιχνίδι η player θα πάρει την αντίστοιχη αρχική τιμή.
ΤΡΙΛΙΖΑ 4

17 # ο παίκτης που θα παίξει πρώτος player player


18 player = "X"
'Χ' 'O' 'X' 'O'
Επανάληψη: η συνθήκη συνέχειας
Σχήμα 5.2: Η τιμή της μεταβλητής
player θα εναλλάσσεται σε κάθε
Ας περάσουμε τώρα στην επαναληπτική δομή. Κάθε κύκλος της επα- γύρο μεταξύ του "X" και του "O", υπο-
νάληψης αντιστοιχεί στη συμπλήρωση ενός τετραγώνου από κά- δεικνύοντας το σύμβολο του παίκτη
ποιο παίκτη. Για να ολοκληρωθεί το παιχνίδι και να τερματιστεί η που έχει σειρά να παίξει. Η αρχική της
τιμή είναι το "Χ".
επανάληψη θα πρέπει είτε να συμπληρωθούν και τα εννέα τετρά-
γωνα, είτε κάποιος από τους δύο παίκτες να κάνει τρίλιζα. Για να
διατυπωθεί λοιπόν η συνθήκη της επανάληψης χρειάζεται το πρό-
γραμμα να καταμετρά το πλήθος των κενών τετραγώνων και να κα-
ταγράφει αν έχει γίνει τρίλιζα.
Για την καταμέτρηση των κενών τετραγώνων θα χρησιμοποιήσουμε
τη μεταβλητή blank. Όταν ξεκινά το παιχνίδι, όλα τα τετράγωνα
είναι κενά.
19 # πλήθος τετραγώνων που απομένουν κενά
20 blank = 9
Για να καταγράφει το πρόγραμμά μας αν έχει γίνει τρίλιζα ή όχι
θα χρησιμοποιήσουμε μια λογική μεταβλητή inarow. Γνωρίζουμε βέ-
βαια πως όταν ξεκινά το παιχνίδι, κανείς από τους δύο παίκτες δεν
έχει κάνει τρίλιζα κι έτσι η αρχική τιμή της inarow θα πρέπει να
είναι False.
21 # λογική μεταβλητή που δείχνει αν έχει γίνει τρίλιζα
22 inarow = False
Είμαστε τώρα έτοιμοι να διατυπώσουμε τη συνθήκη της επανάλη-
ψης: για να συνεχιστεί η επανάληψη θα πρέπει να απομένει τουλά-
χιστον ένα κενό τετράγωνο και ταυτόχρονα κανένας από τους δύο
παίκτες να μην έχει κάνει τρίλιζα.
23 # επανάληψη: συνεχίζεται όσο υπάρχουν κενά τετράγωνα
24 # και δεν έχει γίνει τρίλιζα
25 while blank > 0 and not inarow:

Επανάληψη: επιλογή κίνησης από τον παίκτη

Μέσα στην επανάληψη, στην αρχή κάθε νέου κύκλου, το πρόγραμμα


θα εμφανίζει τον πίνακα της τρίλιζας στον παίκτη που έχει σειρά να
παίξει και θα τον ρωτάει σε ποιο τετράγωνο επιθυμεί να παίξει.
Εδώ υπάρχει ένα πολύ λεπτό σημείο. Έστω ότι ζητάμε από το χρή-
στη να προσδιορίσει μ’ έναν ακέραιο αριθμό το τετράγωνο στο οποίο
θα παίξει. Υπονοείται λοιπόν ότι υπάρχει μια αρίθμηση των τετρα-
γώνων της τρίλιζας, με βάση την οποία θα κάνει την επιλογή του ο
παίκτης. Έχει μεγάλη σημασία ότι η αυτή η αρίθμηση των τετραγώ-
νων αφορά την οπτική γωνία του παίκτη, τον τρόπο με τον οποίο θα
ΤΡΙΛΙΖΑ 5

προσδιορίσει το τετράγωνο που τον ενδιαφέρει και δεν έχει απαραί-


τητα σχέση με την εσωτερική αναπαράσταση της τρίλιζας. 0 1 2

Εμείς όμως προς το παρόν θα επιλέξουμε την απλούστερη δυνατή


3 4 5
αρίθμηση, η οποία ταυτίζεται με την αρίθμηση της εσωτερικής ανα-
παράστασης και φαίνεται στο σχήμα 5.3. Ας έχουμε όμως υπόψη ότι
6 7 8
αυτή η αρίθμηση είναι βολική για εμάς ως προγραμματιστές και δεν
είναι απαραίτητα η βολικότερη αρίθμηση για το χρήστη.
Σχήμα 5.3: Η αρίθμηση των τετραγώ-
Στον κώδικα που ακολουθεί, η print3x3 δεν χρησιμοποιείται μόνο νων της τρίλιζας, από την σκοπιά του
για να εμφανιστεί ο πίνακας της τρίλιζας, αλλά επιστρατεύεται και παίκτη. Με βάση αυτή την αρίθμηση
επιλέγει ο παίκτης το τετράγωνο στο
μια δεύτερη φορά για να εμφανιστεί σε μορφή 3 × 3 πίνακα, σαν
οποίο θα παίξει κάθε φορά. Θα μπο-
βοηθητικό υπόμνημα, η αρίθμηση με βάση την οποία θα επιλέξει τε- ρούσαμε να έχουμε επιλέξει μια δια-
τράγωνο ο παίκτης. φορετική αρίθμηση, αυτή όμως είναι
βολική γιατί ταυτίζεται με την εσωτε-
26 # εμφάνιση πίνακα τρίλιζας και πιθανών κινήσεων ρική μας αναπαράσταση.
27 print3x3(board)
Η range είναι μια ακολουθία τιμών
28 print3x3(range(9)) που ανήκουν σε ένα συγκεκριμένο διά-
29 # επιλογή θέσης από τον παίκτη στημα. Όταν η range καλείται με μια
30 print(player, "διάλεξε τετράγωνο:", end=" ") παράμετρο n, τότε το διάστημα τιμών
31 position = int(input()) είναι από το 0 μέχρι και το n-1.
Εδώ η range(9) είναι η ακολουθία των
Όταν ο χρήστης επιλέξει αριθμό τετραγώνου, το πρόγραμμα θα συ-
τιμών από το 0 μέχρι και το 8, αντι-
μπληρώνει την αναπαράσταση του πίνακα της τρίλιζας με το σύμ- στοιχεί δηλαδή στους αριθμούς των
βολο του παίκτη, ενώ παράλληλα θα μειώνει το πλήθος των κενών θέσεων της τρίλιζας.
τετραγώνων.
32 # ανακοίνωση κίνησης Χρησιμοποιώντας μια εντολή όπως
η board[position] = player τροπο-
33 print("Ο παίκτης", player, "παίζει στο", position) ποιείται το στοιχείο της λίστας board
34 # συμπλήρωση επιλεγμένης θέσης που βρίσκεται στη θέση position, το
35 board[position] = player οποίο αντιστοιχεί πλέον σε μια νέα
36 # μείωση κενών τετραγώνων κατά 1 τιμή, την player. Είναι ένα βασικό χα-
ρακτηριστικό των λιστών ότι τα στοι-
37 blank -= 1
χεία τους μπορούν να τροποποιηθούν.
Η εντολή blank -= 1 μειώνει την τιμή
Επανάληψη: έλεγχος για τρίλιζα της μεταβλητής blank κατά μία μο-
νάδα. Είναι ισοδύναμη με την εντολή
Μετά την κίνηση του παίκτη, το πρόγραμμά μας θα πρέπει να ελέγ- blank = blank - 1. Ανάλογες μορ-
φές μεταβολής μιας μεταβλητής είναι
χει μήπως έγινε τρίλιζα. Προς το παρόν, θα υλοποιήσουμε αυτόν τον διαθέσιμες για την αύξηση, τον πολ-
έλεγχο με τον πιο απλό τρόπο που μπορούμε να σκεφτούμε, εξετάζο- λαπλασιασμό και γενικότερα όλες τις
ντας όλες τις πιθανές τριάδες τετραγώνων. Στη συνέχεια θα έχουμε αριθμητικές πράξεις.
την ευκαιρία να διαπιστώσουμε αν υπάρχουν περιθώρια βελτίωσης.
38 # έλεγχος για τρίλιζα:
39 # για κάθε 3άδα θέσεων στις πιθανές τρίλιζες
40 # ελέγχεται αν ο παίκτης έχει καταλάβει 3 θέσεις
41 if board[0] == board[1] == board[2] == player:
42 inarow = True
43 elif board[3] == board[4] == board[5] == player:
44 inarow = True
45 elif board[6] == board[7] == board[8] == player:
46 inarow = True
ΤΡΙΛΙΖΑ 6

47 elif board[0] == board[3] == board[6] == player: Μέχρι στιγμής έχουμε δει πως οι συ-
γκριτικοί τελεστές χρησιμοποιούνται
48 inarow = True
για να συγκρίνουν δύο τιμές μεταξύ
49 elif board[1] == board[4] == board[7] == player: τους. Στην Python μπορούν να χρη-
50 inarow = True σιμοποιηθούν για να συγκριθούν πολ-
51 elif board[2] == board[5] == board[8] == player: λές τιμές μεταξύ τους, όπως σε αυτό το
52 inarow = True παράδειγμα με τον τελεστή ==. Αυτό
είναι ένα χαρακτηριστικό που δεν το
53 elif board[0] == board[4] == board[8] == player: συναντάμε συχνά σε άλλες γλώσσες
54 inarow = True προγραμματισμού, με τους αντίστοι-
55 elif board[2] == board[4] == board[6] == player: χους συγκριτικούς τελεστές.
56 inarow = True
Κάθε μία από τις οκτώ περιπτώσεις σε αυτή τη δομή επιλογής αντι-
στοιχεί σε έναν από τους οκτώ διαφορετικούς τρόπους να γίνει τρί-
λιζα. Και στις οκτώ περιπτώσεις εκτελείται η ίδια εντολή: η μετα-
βλητή inarow παίρνει την τιμή True, για να καταγραφεί πλέον από
το πρόγραμμα ότι έχει γίνει τρίλιζα (εναλλακτικά, θα μπορούσε να
χρησιμοποιηθεί μια σύζευξη των οκτώ επιμέρους συνθηκών). Παρα-
τηρήστε ότι στη δομή επιλογής δεν υπάρχει else επειδή δε χρειάζε-
ται να εκτελεστεί κάποια εντολή σε περίπτωση που δε γίνει τρίλιζα.

Επανάληψη: εναλλαγή παίκτη

Μέσα στην επανάληψη, απομένει μόνο να εναλλάσσουμε την τιμή


της μεταβλητής player.
57 # εναλλαγή παίκτη
58 if player == "X":
59 player = "O"
60 else:
61 player = "X"

Ανακοίνωση αποτελέσματος

Όταν η επανάληψη τερματιστεί, το παιχνίδι θα έχει τελειώσει και


το πρόγραμμα θα πρέπει να εμφανίσει στους παίκτες ποιο ήταν το
αποτέλεσμα. Υπάρχουν δύο πιθανοί λόγοι για να τελειώσει το παι-
χνίδι: να έχει συμπληρωθεί ο πίνακας του παιχνιδιού ή να έχει γίνει
τρίλιζα (χωρίς το ένα να αποκλείει το άλλο). Αν έχει γίνει τρίλιζα,
θα πρέπει να εμφανίζεται κατάλληλο μήνυμα, διαφορετικά θα πρέ-
πει να εμφανίζεται μήνυμα ότι το παιχνίδι έληξε ισόπαλο.
62 # εμφάνιση τελικού πίνακα τρίλιζας
63 print3x3(board)
64 # εμφάνιση αποτελέσματος
65 if inarow:
66 print("Τρίλιζα!")
67 else:
68 print("Ισοπαλία.")
οχο/src/oxo.1.py
ΤΡΙΛΙΖΑ 7

Είναι υποχρεωτικό το πρόγραμμα να ελέγχει την τιμή της inarow και


όχι της blank για να διαπιστώσει το αποτέλεσμα του παιχνιδιού. Η
τιμή της blank δεν μπορεί να μας οδηγήσει σε ασφαλή συμπερά-
σματα, γιατί όταν έχει την τιμή 0 μετά το τέλος του παιχνιδιού, δεν
ξέρουμε με βεβαιότητα αν το παιχνίδι έληξε ισόπαλο ή αν η τελευ-
ταία κίνηση οδήγησε σε τρίλιζα.

Συμμάζεμα
Ξέρω ότι μπορώ να “σπάσω” το πρόγραμμά μου σε μικρότερα τμήματα,
υλοποιώντας τις επιμέρους λειτουργίες του προγράμματος ως ξεχωριστά
υποπρογράμματα. Από που να ξεκινήσω;
Είναι σημαντικό να διακρίνουμε (σε οποιοδήποτε πρόγραμμα) τις
ομάδες εντολών που λειτουργούν ως ενιαίο σύνολο και υλοποιούν
μια συγκεκριμένη λειτουργία. Αυτά τα τμήματα του προγράμματος
θα αποτελέσουν τη βάση για την κατασκευή των επιμέρους υποπρο-
γραμμάτων.
Ο τρόπος με τον οποίο μπορούμε να διαιρέσουμε ένα ενιαίο πρό-
γραμμα σε επιμέρους τμήματα με βάση τη λειτουργία τους δεν εί-
ναι μοναδικός. Στην πραγματικότητα υπάρχουν πολλές εναλλακτι-
κές. Επίσης, οι εμπειρότεροι προγραμματιστές συνήθως σχεδιάζουν
εκ των προτέρων τα προγράμματά τους και τα υποπρογράμματα από
τα οποία αποτελούνται, χωρίς αυτό να σημαίνει ότι στη συνέχεια,
καθώς υλοποιούν το πρόγραμμά τους, δεν μπορούν να αναθεωρή-
σουν ή να εκλεπτύνουν τον αρχικό τους σχεδιασμό.

Αλληλεπίδραση με το χρήστη

Διατρέχοντας το πρόγραμμα που έχουμε αναπτύξει μέχρι στιγμής,


εντοπίζουμε μέσα στην επαναληπτική δομή μια ομάδα εντολών που
αναλαμβάνει την αλληλεπίδραση με τον παίκτη: εμφανίζει τα τε-
τράγωνα της τρίλιζας και ρωτά τον παίκτη σε ποιο τετράγωνο επι-
θυμεί να παίξει.
# εμφάνιση πίνακα τρίλιζας και πιθανών κινήσεων
print3x3(board)
print3x3(range(9))
# επιλογή θέσης από τον παίκτη
print(player, "διάλεξε τετράγωνο:", end=" ")
position = int(input())
Αυτή η ομάδα εντολών θ’ αποτελέσει τη βάση για τη συνάρτηση
readPosition, η οποία δέχεται σαν παραμέτρους τον παίκτη player
που έχει σειρά να παίξει και την αναπαράσταση board της τρίλιζας
και επιστρέφει τη θέση που επέλεξε ο παίκτης για την επόμενη κί-
νησή του.
ΤΡΙΛΙΖΑ 8

13 def readPosition(player, board): Σε αυτή, αλλά και σε όλες τις συναρ-


τήσεις που ακολουθούν, ονομάσαμε
14 """ Διαβάζει από τον παίκτη τη θέση
player την παράμετρο που αντιστοι-
15 στην οποία επιθυμεί να παίξει και την χεί στο σύμβολο του παίκτη και board
16 επιστρέφει. την παράμετρο που αντιστοιχεί στην
17 player: Ο παίκτης που έχει σειρά να παίξει αναπαράσταση της τρίλιζας. Χρησι-
18 board: Μια λίστα με 9 στοιχεία (η τρίλιζα) μοποιήσαμε δηλαδή τα ίδια ονόματα
που χρησιμοποιούνται και στο κύριο
19 """ πρόγραμμα. Αυτό δεν είναι υποχρεω-
20 # εμφάνιση πίνακα τρίλιζας και πιθανών κινήσεων τικό, θα μπορούσαμε να χρησιμοποιή-
21 print3x3(board) σουμε άλλα ονόματα για τις παραμέ-
22 print3x3(range(9)) τρους, απλά θεωρούμε ότι έτσι το πρό-
γραμμα είναι πιο κατανοητό. Πρέπει
23 # επιλογή θέσης από τον παίκτη όμως να γίνει σαφές ότι δεν πρόκειται
24 print(player, "διάλεξε τετράγωνο:", end=" ") για τις ίδιες μεταβλητές: η player και η
25 position = int(input()) board του κύριου προγράμματος δεν
26 # επιστροφή επιλεγμένης θέσης ταυτίζονται με τις τοπικές player και
board μέσα σε μια συνάρτηση. Μά-
27 return position λιστα οι τοπικές μεταβλητές των συ-
ναρτήσεων δημιουργούνται μόνο όταν
καλείται η συνάρτηση και παύουν να
Πραγματοποίηση κίνησης στο επιλεγμένο τετράγωνο υφίστανται όταν ολοκληρωθεί η εκτέ-
λεση των εντολών της.
Αμέσως μετά τις εντολές που ζητούν από τον παίκτη να επιλέξει
το τετράγωνο στο οποίο θα παίξει, υπάρχει μια ομάδα εντολών που
πραγματοποιούν την κίνηση του παίκτη.
position = int(input())
# ανακοίνωση κίνησης
print("Ο παίκτης", player, "παίζει στο", position)
# συμπλήρωση επιλεγμένης θέσης
Αυτή η ομάδα εντολών θ’ αποτελέσει τη βάση για τη συνάρτηση board
play, η οποία δέχεται σαν παραμέτρους τον παίκτη player που επέ-
λεξε θέση, την αναπαράσταση board της τρίλιζας και τη θέση που
επέλεξε ο παίκτης και πραγματοποιεί την κίνησή του.
28 def play(player, pos, board): player pos board
29 """ Πραγματοποιεί και ανακοινώνει την κίνηση
30 ενός παίκτη σε συγκεκριμένη θέση
31 player: Το σύμβολο του παίκτη
32 pos: Η θέση στην οποία παίζει ο παίκτης Σχήμα 5.4: Όταν κληθεί η συνάρτηση
33 board: Μια λίστα με 9 στοιχεία (η τρίλιζα) play από το κύριο πρόγραμμα, θα
34 """ χρησιμοποιηθεί σαν παράμετρος η λί-
στα board του κύριου προγράμμα-
35 # ανακοίνωση κίνησης τος. Η τοπική μεταβλητή board της
36 print("Ο παίκτης", player, "παίζει στο", pos) συνάρτησης play και η board του
37 # συμπλήρωση επιλεγμένης θέσης κύριου προγράμματος είναι δύο δια-
38 board[pos] = player φορετικές μεταβλητές. Ωστόσο, είναι
στην πραγματικότητα δύο διαφορε-
Η συνάρτηση play δεν επιστρέφει κάποια τιμή, όμως μεταβάλλει την τικά ονόματα για το ίδιο πράγμα:
αναπαράσταση της τρίλιζας (σχήμα 5.4). τη λίστα που αποτελεί την αναπα-
ράσταση της τρίλιζας. Έτσι, οποια-
δήποτε τροποποίηση γίνει εντός της
συνάρτησης στα στοιχεία της λίστας
board, αφορά ουσιαστικά και τη λί-
στα board του κύριου προγράμματος.
ΤΡΙΛΙΖΑ 9

Έλεγχος για τρίλιζα

Μια άλλη ομάδα εντολών που αποτελούν ενιαίο σύνολο απαρτίζε-


ται από τη δομή επιλογής που ελέγχει τις πιθανές τριάδες τετραγώ-
νων για να διαπιστωθεί αν ο παίκτης που έπαιξε έκανε τρίλιζα.
# έλεγχος για τρίλιζα:
# για κάθε 3άδα θέσεων στις πιθανές τρίλιζες
# ελέγχεται αν ο παίκτης έχει καταλάβει 3 θέσεις
if board[0] == board[1] == board[2] == player:
inarow = True
elif board[3] == board[4] == board[5] == player:
inarow = True
...
inarow = True
elif board[2] == board[4] == board[6] == player:
inarow = True
Αυτή η ομάδα εντολών θ’ αποτελέσει τη βάση για τη συνάρτηση
check, η οποία δέχεται σαν παραμέτρους τον παίκτη player που
έχει σειρά να παίξει και την αναπαράσταση board της τρίλιζας κι
επιστρέφει True ή False ανάλογα αν ο παίκτης έκανε τρίλιζα ή όχι.
39 def check(player, board):
40 """ Ελέγχει αν υπάρχει 3άδα όπου ο παίκτης player
41 έχει καταλάβει και τις 3 θέσεις.
42 player: σύμβολο παίκτη ("Χ" ή "Ο")
43 board: Μια λίστα με 9 στοιχεία (η τρίλιζα)
44 """
45 # για κάθε 3άδα θέσεων στις πιθανές τρίλιζες
46 # αν ο παίκτης έχει καταλάβει και τις 3 θέσεις
47 if board[0] == board[1] == board[2] == player:
48 return True
49 elif board[3] == board[4] == board[5] == player:
50 return True
51 elif board[6] == board[7] == board[8] == player:
52 return True
53 elif board[0] == board[3] == board[6] == player:
54 return True
55 elif board[1] == board[4] == board[7] == player:
56 return True
57 elif board[2] == board[5] == board[8] == player:
58 return True
59 elif board[0] == board[4] == board[8] == player:
60 return True
61 elif board[2] == board[4] == board[6] == player:
62 return True
63 else:
64 # αν φτάσουμε μέχρι εδώ, ο έλεγχος απέτυχε
65 return False
ΤΡΙΛΙΖΑ 10

Εναλλαγή παίκτη

Τέλος, οι εντολές που φροντίζουν για την εναλλαγή του παίκτη στο
τέλος κάθε κύκλου της επανάληψης αποτελούν επίσης ένα ενιαίο
σύνολο εντολών.
# εναλλαγή παίκτη
if player == "X":
player = "O"
else:
player = "X"
Αυτή η ομάδα εντολών θ’ αποτελέσει τη βάση για τη συνάρτηση
next, η οποία δέχεται σαν παράμετρο έναν παίκτη player και επι-
στρέφει τον παίκτη που έχει σειρά να παίξει μετά από αυτόν.
66 def next(player):
67 """ Επιστρέφει το σύμβολο του παίκτη που παίζει
68 μετά τον παίκτη με σύμβολο player.
69 player: σύμβολο παίκτη ("Χ" ή "Ο")
70 """
71 if player == "X":
72 return "O"
73 else:
74 return "X"

Το κύριο πρόγραμμα

Τώρα, το κύριο πρόγραμμα μπορεί να καλεί αυτές τις συναρτήσεις.


Στο σημείο όπου το πρόγραμμα αλληλεπιδρά με το χρήστη για να
τον ρωτήσει σε ποιο τετράγωνο επιθυμεί να παίξει, καλείται η συ-
νάρτηση readPosition. Στο σημείο όπου πραγματοποιείται η κί-
νηση του παίκτη, καλείται η play. Στο σημείο όπου ελέγχεται αν η
κίνηση του παίκτη οδηγεί σε τρίλιζα, καλείται η check. Τέλος, αμέ-
σως μετά, στο σημείο όπου εναλλάσσεται ο παίκτης που έχει σειρά
να παίξει, καλείται η next.
85 # επανάληψη: συνεχίζεται όσο υπάρχουν κενά τετράγωνα
86 # και δεν έχει γίνει τρίλιζα
87 while blank > 0 and not inarow:
88 # επιλογή θέσης από τον παίκτη
89 position = readPosition(player, board)
90 # συμπλήρωση επιλεγμένης θέσης
91 play(player, position, board)
92 # μείωση κενών τετραγώνων κατά 1
93 blank -= 1
94 # έλεγχος για τρίλιζα
95 inarow = check(player, board)
96 # εναλλαγή παίκτη
97 player = next(player)
ΤΡΙΛΙΖΑ 11

Τώρα πλέον το μέγεθος του κύριου προγράμματος έχει μειωθεί δρα-


ματικά, ενώ έχει βελτιωθεί κατά πολύ η αναγνωσιμότητά του.

Το Ζήτημα του Νικητή


Θα ήθελα το πρόγραμμα ν’ ανακοινώνει ποιος παίκτης κέρδισε.
Σε περίπτωση που γίνει τρίλιζα, το πρόγραμμα εμφανίζει το μήνυμα
"Τρίλιζα!", χωρίς όμως ν’ ανακοινώνει ποιος από τους δύο παίκτες
κέρδισε. Προφανώς, πρόκειται για τον παίκτη που έπαιξε τελευταίος
κι έτσι μια βιαστική λύση θα ήταν να εμφανίσουμε το νικητή τρο-
ποποιώντας τις εντολές εμφανίζουν το αποτέλεσμα ως εξής:
# εμφάνιση αποτελέσματος
if inarow:
print("Τρίλιζα! Κέρδισε ο", player)
else:
print("Ισοπαλία.")
Εδώ όμως υπάρχει πρόβλημα. Η μεταβλητή player εναλλάσσεται
στο τέλος κάθε κύκλου της επανάληψης, ακόμα κι όταν κάποιος παί-
κτης κάνει τρίλιζα. Έτσι, μετά τον τερματισμό της επανάληψης, η
player δεν αντιστοιχεί στον παίκτη που έπαιξε τελευταίος (και κέρ-
δισε), αλλά στον επόμενο (που είναι ο ηττημένος). Επομένως, αν το
πρόγραμμα απλά εμφανίσει την τιμή της player, θα εμφανίσει τον
παίκτη που έχασε, όχι εκείνον που έκανε τρίλιζα.
Για να διορθώσουμε αυτή την συμπεριφορά θα υιοθετήσουμε μια
λύση που απαιτεί τις λιγότερες αλλαγές στο πρόγραμμά μας: ο νικη-
τής του παιχνιδιού είναι ο παίκτης που θα έπαιζε μετά τον ηττημένο.
100 # εμφάνιση αποτελέσματος
101 if inarow:
102 print("Τρίλιζα! Κέρδισε ο", next(player))
103 else:
104 print("Ισοπαλία.")
oxo/src/oxo.2.py

Το Ζήτημα του Ελέγχου


Όταν ο χρήστης επιλέγει το τετράγωνο στο οποίο θα παίξει, το πρό-
γραμμα του επιτρέπει να πληκτρολογήσει οποιονδήποτε αριθμό, χωρίς
κανέναν έλεγχο αν αυτός βρίσκεται μεταξύ 0 και 8. Δεν ελέγχεται καν
αν το τετράγωνο που επιλέγει ο χρήστης είναι κατειλημμένο.
Η επιλογή τετραγώνου από τον παίκτη πραγματοποιείται μέσω της
συνάρτησης readPosition. Αυτή θα πρέπει τώρα να επεκταθεί, έτσι
ώστε η τιμή που πληκτρολογεί ο παίκτης να ελέγχεται και, σε περί-
πτωση που δεν είναι έγκυρη, να ζητείται νέα τιμή.
Μέσα στη readPosition, οι εντολές που ζητούν από τον παίκτη να
επιλέξει το τετράγωνο στο οποίο θα παίξει θα τοποθετηθούν πλέον
μέσα σε μια επαναληπτική δομή. Έτσι, ο παίκτης θα “εγκλωβίζεται”
ΤΡΙΛΙΖΑ 12

σε έναν κύκλο από τον οποίο βγαίνει μόνο όταν πληκτρολογήσει μια
έγκυρη τιμή. Αν ο παίκτης επιλέξει θέση που δεν είναι ανάμεσα στο
0 και το 8 ή επιλέξει κατειλημμένη θέση τότε εμφανίζεται μήνυμα
λάθους και η ανάγνωση τιμής επαναλαμβάνεται.
20 # εμφάνιση πίνακα τρίλιζας και πιθανών κινήσεων
21 print3x3(board)
22 print3x3(range(9))
23 # επανάληψη: όσο το τετράγωνο δεν είναι έγκυρο
24 while True:
25 # επιλογή θέσης από τον παίκτη
26 print(player, "διάλεξε τετράγωνο:", end=" ")
27 position = int(input())
28 # έλεγχος εγκυρότητας
29 if position < 0 or position > 8:
30 print("Επίλεξε τιμή μεταξύ 0 και 8.")
31 elif board[position] != " ":
32 print("To", position, "δεν είναι κενό.")
33 else:
34 # έγκυρη τιμή, τέλος επανάληψης
35 break
36 # επιστροφή επιλεγμένης θέσης
37 return position
oxo/src/oxo.3.py

Στο κύριο πρόγραμμα δεν απαιτείται καμία αλλαγή. Εκεί εξακολου-


θεί να καλείται η readPosition, από την οποία προκύπτει η θέση
στην οποία θα τοποθετήσει το σύμβολό του ο παίκτης. Όμως τώρα
η τιμή που θα επιστρέψει η τροποποιημένη readPosition θα είναι
σίγουρα έγκυρη.

Το Ζήτημα της Τρίλιζας


Η συνάρτηση check μου κάθεται στο λαιμό. Δεν γίνεται να υλοποιήσουμε
τον έλεγχο αν έχει γίνει τρίλιζα με πιο κομψό, συμπαγή τρόπο;
Αν μελετήσουμε τη μεγάλη if που βρίσκεται μέσα στη συνάρτηση Μια δομή δεδομένων που μοιάζει πολύ
check και ελέγχει τους οκτώ διαφορετικούς τρόπους να γίνει τρί- με τις λίστες είναι οι πλειάδες (tuples).
λιζα, θα παρατηρήσουμε ότι οι οκτώ συνθήκες που ελέγχονται είναι Τα περιεχόμενα των πλειάδων περι-
κλείονται σε παρενθέσεις και, σε αντί-
ουσιαστικά ίδιες μεταξύ τους. Κάθε συνθήκη εξετάζει μια τριάδα θέ-
θεση με τις λίστες, δεν μπορούν να
σεων και το μόνο που αλλάζει από περίπτωση σε περίπτωση είναι μεταβληθούν. Η πρόσβαση στα στοι-
οι αριθμοί των τριών θέσεων που ελέγχονται. χεία των πλειάδων γίνεται ακριβώς με
τον ίδιο τρόπο όπως και στις λίστες.
Ας καταγράψουμε λοιπόν σε μια κατάλληλη δομή τις διαφορετικές
τριάδες θέσεων που εξετάζονται. Εδώ θέλουμε ν’ αποτυπώσουμε τις
διαφορετικές τριάδες θέσεων που
# οι 3άδες των θέσεων που σχηματίζουν τρίλιζες σχηματίζουν τρίλιζες. Επειδή αυτές
triples = ( οι τιμές δεν πρόκειται ν’ αλλάξουν,
χρησιμοποιείται για την αποθήκευσή
(0,1,2),(3,4,5),(6,7,8), # οριζόντια τους μια πλειάδα, η triples.
(0,3,6),(1,4,7),(2,5,8), # κάθετα
Η triples είναι μια πλειάδα που πε-
(0,4,8),(2,4,6)) # διαγώνια
ριέχει άλλες πλειάδες. Συγκεκριμένα,
περιέχει τριάδες με τους αριθμούς των
θέσεων που σχηματίζουν τρίλιζες.
ΤΡΙΛΙΖΑ 13

Αυτό το τμήμα κώδικα που ορίζει την triples, μπορούμε να το ει-


σάγουμε στη συνάρτηση check. Στην περίπτωση αυτή, θα πρόκει-
ται για μια τοπική μεταβλητή της συνάρτησης. Θα δημιουργείται εκ
νέου κάθε φορά που θα καλείται η check και θα παύει να υφίσταται
όταν τελειώνει η εκτέλεσή της.
Εμείς όμως θα προτιμήσουμε να ορίσουμε την triples στην αρχή
του κύριου προγράμματος. Θα είναι έτσι μια καθολική μεταβλητή. Θα
μπορούν να αναφερθούν σε αυτήν (αλλά όχι να την τροποποιήσουν)
όλες οι συναρτήσεις, συμπεριλαμβανομένης και της check φυσικά.
Διατρέχοντας τις οκτώ τριάδες που περιέχει η triples, μπορούμε να
ελέγξουμε επαναληπτικά, για κάθε μια από αυτές, αν είναι πλήρως
συμπληρωμένη από έναν συγκεκριμένο παίκτη. Η συνάρτηση check
μπορεί πλέον να ξαναγραφτεί με πιο συμπαγή τρόπο.
Η εντολή for είναι μια εντολή επανά-
49 def check(player, board):
ληψης που διατρέχει τα στοιχεία μιας
50 """ Ελέγχει αν υπάρχει 3άδα όπου ο παίκτης player ακολουθίας τιμών, όπως μια λίστα ή
51 έχει καταλάβει και τις 3 θέσεις. μια πλειάδα, με τη σειρά με την οποία
52 player: σύμβολο παίκτη ("Χ" ή "Ο") αυτά εμφανίζονται στην ακολουθία.
53 board: Μια λίστα με 9 στοιχεία (η τρίλιζα) Σε κάθε επανάληψη, η τιμή του επόμε-
νου στοιχείου της ακολουθίας ανατί-
54 """ θεται στη μεταβλητή απαρίθμησης που
55 # για κάθε 3άδα θέσεων χρησιμοποιούμε στη for.
56 for triple in triples:
Εδώ η for χρησιμοποιείται για να δια-
57 # p1, p2 και p3 οι τρεις θέσεις της 3άδας τρεχθούν όλες οι τριάδες της πλειά-
58 p1, p2, p3 = triple δας triples.
59 # αν ο παίκτης έχει καταλάβει και τις 3 θέσεις Με την εντολή p1, p2, p3 = triple
60 if board[p1] == board[p2] == board[p3] == player: οι τρεις τιμές που βρίσκονται αποθη-
61 return True κευμένες στην πλειάδα triple αποδί-
62 # αν φτάσουμε μέχρι εδώ, ο έλεγχος απέτυχε δονται στις μεταβλητές p1, p2 και p3
αντίστοιχα.
63 return False
οχο/src/oxo.4.py Αυτό ονομάζεται ξεπακετάρισμα της
πλειάδας (tuple unpacking) και είναι
Αν και η συνάρτηση check ουσιαστικά ξαναγράφτηκε από την αρχή, ουσιαστικά ο μηχανισμός που χρησι-
η λειτουργία της παρέμεινε η ίδια: ελέγχει αν έχει γίνει τρίλιζα. Γι’ μοποιείται και για την επιστροφή πολ-
αυτό και δεν απαιτείται καμία τροποποίηση στο κύριο πρόγραμμα. λαπλών τιμών από συναρτήσεις.
Αυτό που άλλαξε είναι η υλοποίηση αυτής της λειτουργίας, δηλαδή
ο τρόπος με τον οποίο ελέγχεται αν έχει γίνει τρίλιζα.

Κάτι Για Παρέα


Στην αρχή είπαμε ότι θα φτιάξουμε ένα πρόγραμμα που θα παίζει τρίλιζα.
Προς το παρόν και οι δύο παίκτες που συμμετέχουν στο παιχνίδι είναι
άνθρωποι.
Θα κάνουμε τις απαραίτητες επεκτάσεις, ώστε το πρόγραμμα ν’ ανα-
λάβει το ρόλο ενός από τους δύο παίκτες. Δεν είναι ανάγκη να προ-
σπαθήσουμε εξαρχής να κάνουμε το πρόγραμμα να παίζει έξυπνα.
Με αυτό θα ασχοληθούμε στο επόμενο βήμα· προς το παρόν, θα του
επιτρέψουμε να ξεκινάει πρώτο και να επιλέγει σε κάθε γύρο μια
τυχαία κίνηση.
ΤΡΙΛΙΖΑ 14

Αφού οι κινήσεις του προγράμματος θα είναι τυχαίες, χρειάζεται να


εισαγάγουμε, στην αρχή του προγράμματος, την κατάλληλη βιβλιο-
θήκη.
1 import random

Οι διαθέσιμες θέσεις

Για να επιλέξει το πρόγραμμα σε ποια θέση θα παίξει, θα πρέπει


πρώτα να υπολογίσει ποιες από τις εννέα θέσεις είναι διαθέσιμες.
Αυτό μπορεί να γίνει με τον κώδικα που ακολουθεί, ο οποίος διατρέ-
χει τις εννέα θέσεις της τρίλιζας και κατασκευάζει μια λίστα με τις
θέσεις εκείνες που είναι διαθέσιμες.
Η range χρησιμοποιείται για να ανα-
# η λίστα με τις διαθέσιμες θέσεις, αρχικά κενή
φερθούμε σε ένα διάστημα τιμών. Η
positions = [] προαιρετική πρώτη παράμετρος ορί-
# για κάθε θέση από 0 μέχρι και 8 ζει το αριστερό άκρο του διαστήμα-
for s in range(9): τος. Αν παραληφθεί, το διάστημα ξε-
# αν η θέση s είναι διαθέσιμη κινά από το μηδέν. Η δεύτερη παρά-
μετρος ορίζει το δεξί άκρο, το οποίο
if board[s] == " ": είναι ανοικτό (δηλαδή το διάστημα
# προστίθεται στη λίστα διαθέσιμων θέσεων δεν περιλαμβάνει την τιμή που δίνεται
positions.append(s) σαν δεξί άκρο). Η προαιρετική τρίτη
παράμετρος ορίζει το βήμα, δηλαδή
Είναι συνηθισμένο να κατασκευάζουμε λίστες με αυτόν ακριβώς τον ανά πόσες τιμές του διαστήματος θα
τρόπο, ξεκινώντας από μια κενή λίστα στην οποία προσθέτουμε στα- διατρέχονται.
διακά τις τιμές που ικανοποιούν ένα συγκεκριμένο κριτήριο. Είναι Η μέθοδος append() εφαρμόζεται σε
τόσο συνηθισμένο αυτό το μοτίβο, που υπάρχει κι ένας εναλλακτι- μια λίστα και προσθέτει ένα νέο στοι-
κός, συνοπτικότερος τρόπος να δημιουργηθεί η ίδια λίστα: χείο στο τέλος της.
Εδώ η append() καλείται επανα-
# συγκέντρωση λίστας: λίστα με τις διαθέσιμες θέσεις s
ληπτικά, κατασκευάζοντας στοιχείο
positions = [s for s in range(9) if board[s] == " "] προς στοιχείο τη λίστα με τις διαθέ-
σιμες θέσεις.
Ουσιαστικά εδώ περιγράφουμε από τι αποτελείται η positions, όπως
θα κάναμε στα μαθηματικά: είναι μια λίστα από όλες τις θέσεις s, Η συγκέντρωση λίστας (list
μεταξύ 0 και 8, για τις οποίες ισχύει ότι το board[s] είναι κενό, δη- comprehension), είναι ένας συ-
νοπτικός και εύληπτος τρόπος να
λαδή η αντίστοιχη θέση της τρίλιζας είναι διαθέσιμη. δημιουργήσουμε μια λίστα περιγρά-
Τώρα μπορούμε να ορίσουμε τη συνάρτηση available, η οποία δέ- φοντας τα στοιχεία της, από που αυτά
προέρχονται και πως επιλέγονται.
χεται σαν παράμετρο την αναπαράσταση board της τρίλιζας και
επιστρέφει μια νέα λίστα με τους αριθμούς των διαθέσιμων θέσεων. Εδώ συγκεντρώνουμε σε μια λίστα
όλα τα στοιχεία s που περιέχονται
79 def randomPosition(board): στο διάστημα μεταξύ 0 και 8 και έχουν
80 """ Επιστρέφει μια τυχαία διαθέσιμη θέση το χαρακτηριστικό ότι το board[s]
έχει την τιμή " " (κενό).
81 board: Μια λίστα με 9 στοιχεία (η τρίλιζα)
82 """
83 return random.choice(available(board))

Τυχαία επιλογή θέσης

Έχοντας κατασκευάσει τη συνάρτηση available, η οποία επιστρέ-


φει μια λίστα με τις διαθέσιμες θέσεις, είναι εύκολο να προχωρή-
ΤΡΙΛΙΖΑ 15

σουμε και στον ορισμό της συνάρτησης randomPosition, η οποία


δέχεται σαν παράμετρο την αναπαράσταση board της τρίλιζας και
επιστρέφει μια τυχαία επιλεγμένη διαθέσιμη θέση.
Η συνάρτηση choice() της βιβλιο-
79 def randomPosition(board):
θήκης random δέχεται σαν παράμετρο
80 """ Επιστρέφει μια τυχαία διαθέσιμη θέση μια λίστα κι επιστρέφει ένα τυχαία
81 board: Μια λίστα με 9 στοιχεία (η τρίλιζα) επιλεγμένο στοιχείο της.
82 """ Εδώ χρησιμοποιείται για την τυχαία
83 return random.choice(available(board)) επιλογή μιας θέσης, από τη λίστα των
διαθέσιμων θέσεων.

Επεκτάσεις στο κύριο πρόγραμμα

Ως τώρα, έχουμε προσθέσει στο πρόγραμμα την απαραίτητη υπο-


δομή ώστε να είναι δυνατή η επιλογή μιας τυχαίας θέσης. Ωστόσο,
το κύριο πρόγραμμα εξακολουθεί να υλοποιεί ένα παιχνίδι δύο παι-
κτών. Θα πρέπει λοιπόν να επεκτείνουμε το κύριο πρόγραμμα, ώστε
ν’ αναλαμβάνει πλέον το ρόλο ενός από τους δύο παίκτες.
Θα χρησιμοποιήσουμε τη μεταβλητή computer, της οποίας η τιμή
θ’ αντιστοιχεί στο σύμβολο του παίκτη που θα παίζει αυτοματοποι-
ημένα. Στην αρχή του παιχνιδιού η computer θα παίρνει την τιμή
"X", ώστε το πρόγραμμα να παίζει πρώτο.
95 # ο παίκτης που θα κατευθύνεται από το πρόγραμμα
96 computer = "X"
Θυμηθείτε ότι η μεταβλητή player εναλλάσσεται σε κάθε γύρο με- player player
ταξύ των τιμών "X" και "O", υποδεικνύοντας ποιος παίκτης έχει
σειρά να παίξει στον επόμενο γύρο. Συκρίνοντας λοιπόν την player
με την computer ελέγχουμε ουσιαστικά αν ο επόμενος παίκτης είναι
ο άνθρωπος ή το πρόγραμμά μας. Αν είναι σειρά του προγράμμα- computer computer
τος να παίξει, καλείται η randomPosition, ενώ σε διαφορετική πε- (α) (β)
ρίπτωση καλείται η readPosition, ώστε να επιλέξει ο χρήστης τη Σχήμα 5.5: Επιλογή κίνησης, ανάλογα
θέση στην οποία επιθυμεί να παίξει. με τον παίκτη: (α) αν η computer έχει
ίδια τιμή με την player, τότε είναι
104 if player == computer: σειρά του προγράμματος να παίξει,
105 # επιλογή θέσης από το πρόγραμμα ενώ (β) σε διαφορετική περίπτωση,
106 position = randomPosition(board) έχει σειρά να παίξει ο χρήστης.
107 else:
108 # επιλογή θέσης από τον παίκτη
109 position = readPosition(player, board)
oxo/src/oxo.5.py

Κάτι “Εξυπνότερο” Για Παρέα


Ωραία, τώρα το πρόγραμμμα παίζει με τον παίκτη, αλλά οι κινήσεις του
είναι αστείες. Παίζει τυχαία ακόμα κι όταν του δίνεται η ευκαιρία να
κερδίσει. Παίζει τυχαία ακόμα κι όταν ο αντίπαλός του ετοιμάζεται να
κάνει τρίλιζα. Θέλω να παίζει “εξυπνότερα”.
Το λιγότερο που μπορούμε να κάνουμε είναι να επεκτείνουμε το πρό-
γραμμα έτσι ώστε να ελέγχει πότε ένας παίκτης έχει τη δυνατότητα
ΤΡΙΛΙΖΑ 16

να κάνει τρίλιζα. Έτσι, όταν έχει την ευκαιρία, το πρόγραμμα θα


μπορεί να κερδίζει ή, τουλάχιστον, να εμποδίζει τον αντίπαλό του.
Ένας παίκτης έχει τη δυνατότητα να κάνει τρίλιζα όταν υπάρχει
τριάδα στην οποία οι δύο θέσεις είναι ήδη κατειλημμένες από τον
παίκτη και η τρίτη είναι κενή.
Η συνάρτηση check που περιέχει ήδη το πρόγραμμά μας ελέγχει
κάτι παρεμφερές: αν υπάρχει τριάδα στην οποία και οι τρεις θέσεις
είναι ήδη κατειλημμένες από έναν παίκτη.
Μπορούμε λοιπόν να βασιστούμε στον κώδικα της check, κάνοντας
τις κατάλληλες τροποποιήσεις, και να φτιάξουμε τη νέα συνάρτηση
checkPartial, η οποία δέχεται σαν παραμέτρους τον παίκτη player
που έχει σειρά να παίξει και την αναπαράσταση board της τρίλιζας
και ελέγχει αν ο παίκτης έχει τη δυνατότητα να κάνει τρίλιζα. Η
συνάρτηση επιστρέφει τον αριθμό της θέσης στην οποία πρέπει να
παίξει ο παίκτης player για να κάνει τρίλιζα ή, αν δεν υπάρχει τέ-
τοια θέση, την ειδική τιμή None.
Η χρήση των παρενθέσεων στις συν-
65 def checkPartial(player, board):
θήκες της if δεν είναι υποχρεωτική.
66 """ Ελέγχει αν υπάρχει 3άδα όπου ο παίκτης player Είναι όμως ένας από τους πιθανούς
67 έχει καταλάβει τις 2 από τις τρεις θέσεις. τρόπους να υποδηλώσουμε ότι πρό-
68 Επιστρέφει την 3η κενή θέση της 3άδας ή None. κειται για “μεγάλες” γραμμές που συ-
69 player: σύμβολο παίκτη ("Χ" ή "Ο") νεχίζονται και στην επόμενη γραμμή.
Ένας άλλος τρόπος είναι η χρήση της
70 board: Μια λίστα με 9 στοιχεία (η τρίλιζα) καθέτου \ στο τέλος κάθε τέτοιας με-
71 """ γάλης γραμμής.
72 # για κάθε 3άδα θέσεων
73 for triple in triples:
74 # p1, p2 και p3 οι τρεις θέσεις της 3άδας
75 p1, p2, p3 = triple
76 # αν ο παίκτης κατέχει 2 από τις 3 θέσεις
77 if (board[p1] == board[p2] == player and
78 board[p3] == " "):
79 return p3
80 elif (board[p1] == board[p3] == player and
81 board[p2] == " "):
82 return p2
83 elif (board[p2] == board[p3] == player and
84 board[p1] == " "):
85 return p1
86 # αν φτάσουμε μέχρι εδώ, ο έλεγχος απέτυχε
87 return None
Η checkPartial, όπως και η check, διατρέχει τις τριάδες θέσεων
που σχηματίζουν τρίλιζες. Όμως η checkPartial ελέγχει, για κάθε
τριάδα, αν είναι σχεδόν συμπληρωμένη από έναν παίκτη και επι-
στρέφει τον αριθμό της θέσης που απομενει να συμπληρωθεί.
ΤΡΙΛΙΖΑ 17

Τυχαιότητα με Μέτρο

Έχοντας στη διάθεσή μας την checkPartial, μπορούμε να επεκτεί-


νουμε την randomPosition έτσι ώστε ο παίκτης με τα Χ να παίζει
τυχαία μόνο όταν δεν έχει την ευκαιρία να νικήσει ή να εμποδίσει
τον αντίπαλό του.
Επειδή αυτή η ευκαιρία παρουσιάζεται μετά τη δεύτερη κίνηση, η
randomPosition θα ξεκινά πλέον με τον κώδικα που ακολουθεί:
Η μέθοδος count εφαρμόζεται σε μια
108 # πόσες κινήσεις έχει κάνει ο Χ;
λίστα. Δέχεται σαν παράμετρο μια
109 nbMoves = board.count("X") τιμή και επιστρέφει το πλήθος των εμ-
φανίσεων αυτής της τιμής στη λίστα.
Αν έχουν γίνει τουλάχιστον δύο κινήσεις, ελέγχεται αν υπάρχει θέση
στην οποία μπορεί να παίξει ο παίκτης με τα X και να κάνει τρίλιζα. Εδώ η count χρησιμοποιείται για να
Αν υπάρχει, η randomPosition επιστρέφει αυτή τη θέση, αντί μιας μετρήσουμε το πλήθος των "X" στη λί-
στα board, δηλαδή πόσες φορές έχει
τυχαίας, επιτρέποντας στον παίκτη με τα Χ να κερδίσει. ήδη παίξει ο παίκτης με τα Χ.
110 # αν ο "Χ" έχει κάνει περισσότερες από 2 κινήσεις
111 if nbMoves >= 2:
112 # έλεγξε αν ο "Χ" μπορεί να κάνει τρίλιζα
113 position = checkPartial("X", board)
114 if position is not None:
115 return position
Στη συνέχεια, ελέγχεται αν υπάρχει θέση στην οποία μπορεί να παί-
ξει ο παίκτης με τα Ο και να κάνει τρίλιζα. Αν υπάρχει, επιστρέφεται
αυτή η θέση, αντί μιας τυχαίας, επιτρέποντας στον παίκτη με τα Χ
να εμποδίσει τον αντίπαλό του από το να κάνει τρίλιζα.
116 # έλεγξε αν ο "Ο" μπορεί να κάνει τρίλιζα
117 position = checkPartial("O", board)
118 if position is not None:
119 return position
120 # διαφορετικά επίλεξε τυχαία μια διαθέσιμη θέση
121 return random.choice(available(board))
oxo/src/oxo.6.py

Τώρα η randomPosition επιστρέφει μια τυχαία θέση μόνο αν δεν


προκύψει αποτέλεσμα από την όλη διαδικασία που προηγείται.

Το Έξυπνο Χαρτί
Τίποτα καλύτερο δε γίνεται; Εγώ ξέρω ότι οι υπολογιστές είναι αχτύπητοι
σε τέτοιου είδους παιχνίδια.
Συνήθως τα προγράμματα που παίζουν τέτοια παιχνίδια χρειάζε-
ται να κάνουν αναζήτηση για να παίξουν έξυπνα. Αυτό σημαίνει ότι
δοκιμάζουν πολλούς διαφορετικούς συνδυασμούς κινήσεων για να
καταλήξουν στην επόμενη κίνηση που θα επιλέξουν. Όμως η τρίλιζα
είναι πολύ απλό παιχνίδι και δε χρειάζεται αναζήτηση. Αρκούν ορι-
σμένες απλές οδηγίες για να παίξει κανείς ικανοποιητικά.
ΤΡΙΛΙΖΑ 18

Εμείς θα δανειστούμε τις οδηγίες που περιγράφονται σε μια διασκε- Έξυπνο χαρτί: pythonies.mysch.gr/
δαστική δραστηριότητα που ονομάζεται Το Έξυπνο Χαρτί. Και πάλι, ipaper.pdf και οι επεκτάσεις του:
ipaper-ext.pdf
θεωρούμε ότι το πρόγραμμά μας θα παίξει πρώτο, χρησιμοποιώντας
το σύμβολο Χ.

Κίνηση 1: Γράψε το Χ σε οποιαδήποτε γωνία.


Κίνηση 2: Αν η γωνία που βρίσκεται διαγωνίως απέναντι
από το πρώτο Χ είναι ελεύθερη, τότε γράψε εκεί το Χ,
αλλιώς γράψε το Χ σε οποιαδήποτε ελεύθερη γωνία.
Κινήσεις 3 και 4: Αν μπορείς, κάνε τρίλιζα με τα Χ. Διαφο-
ρετικά, έλεγξε αν ο αντίπαλος μπορεί να κάνει τρίλιζα
και γράψε το Χ έτσι ώστε να τον εμποδίσεις. Αν κανένας
δεν μπορεί να κάνει τρίλιζα, γράψε το Χ σε οποιαδήποτε
ελεύθερη γωνία.
Κίνηση 5: Γράψε το Χ στο ελεύθερο τετράγωνο.
Η συνάρτηση paperPosition που ακολουθεί δέχεται σαν παράμε-
τρο την αναπαράσταση board της τρίλιζας και επιστρέφει τη θέση
στην οποία πρέπει να παίξει ο παίκτης με τα Χ, σύμφωνα με τις οδη-
γίες του έξυπνου χαρτιού.
Αυτή η συνάρτηση θ’ αντικαταστήσει την randomPosition που επι-
λέγει τυχαίες κινήσεις.
100 def paperPosition(board):
101 """ Επιστρέφει τον αριθμό της θέσης όπου πρέπει
102 να παίξει ο Χ, σύμφωνα με το "έξυπνο χαρτί".
103 board: Μια λίστα με 9 στοιχεία (η τρίλιζα)
104 """
Οι οδηγίες του έξυπνου χαρτιού διαφοροποιούνται ανάλογα με το
πλήθος των κινήσεων που έχει πραγματοποιήσει ο παίκτης με τα Χ.
105 # πόσες κινήσεις έχει κάνει ο Χ;
106 nbMoves = board.count("X")
Τώρα το πρόγραμμά μας μπορεί να ελέγχει πόσες κινήσεις έχει ήδη
κάνει ο παίκτης με τα Χ, και να επιλέγει την επόμενη κίνησή του με
βάση τις οδηγίες του έξυπνου χαρτιού.
Στην πρώτη κίνηση, το έξυπνο χαρτί προτρέπει τον παίκτη με τα Χ
να επιλέξει οποιαδήποτε γωνία. Το πρόγραμμά μας θα επιλέγει την
επάνω αριστερά γωνία.
107 if nbMoves == 0:
108 # κίνηση 1η: πάνω αριστερά γωνία (θέση 0)
109 return 0
Στη δεύτερη κίνηση, ο παίκτης με τα Χ διαθέτει δύο επιλογές: Αν
είναι διαθέσιμη η κάτω δεξιά γωνία (που βρίσκεται διαγωνίως απέ-
ναντι από τη γωνία που επέλεξε στην πρώτη του κίνηση), τότε θα
ΤΡΙΛΙΖΑ 19

επιλέγει να παίξει εκεί. Σε διαφορετική περίπτωση, το πρόγραμμά


μας θα επιλέγει την επάνω δεξιά γωνία.
110 elif nbMoves == 1:
111 # κίνηση 2η: κάτω δεξιά γωνία (θέση 8), αν
112 # είναι διαθέσιμη, αλλιώς πάνω δεξιά (θέση 2)
113 if board[8] == " ":
114 return 8
115 else:
116 return 2
Ο κώδικας για τις επόμενες δύο κινήσεις μας είναι γνώριμος από την
randomPosition. Αρχικά το πρόγραμμα ελέγχει αν ο παίκτης με τα
Χ (δηλαδή το ίδιο το πρόγραμμα) μπορεί να κάνει τρίλιζα.
117 elif nbMoves < 4:
118 # κίνηση 3η, 4η: αν ο "X" κάνει τρίλιζα
119 position = checkPartial("X", board)
120 if position is not None:
121 return position
Αν ο Χ δεν μπορεί να κάνει τρίλιζα, ελέγχει αν μπορεί ο παίκτης με
τα Ο, ώστε να τον εμποδίσει.
122 # έλεγξε αν ο "Ο" μπορεί να κάνει τρίλιζα
123 position = checkPartial("O", board)
124 if position is not None:
125 return position
Αν τίποτε από τα παραπάνω δεν μπορεί να γίνει, το πρόγραμμα επι-
λέγει την πρώτη διαθέσιμη γωνία.
126 # αλλιώς παίξε σε οποιαδήποτε ελεύθερη γωνία
127 for corner in (0,2,6,8):
128 if board[corner] == " ":
129 return corner
Αν το παιχνίδι έχει φτάσει μέχρι την τελευταία κίνηση, τότε είναι
δεδομένο ότι έχει μείνει μόνο μία διαθέσιμη θέση, η οποία θα απο-
τελεί το μοναδικό στοιχείο της λίστας possible. Αυτή είναι και η
θέση που επιλέγει αναγκαστικά το πρόγραμμά μας.
Η μέθοδος index εφαρμόζεται σε μια
130 else:
λίστα. Δέχεται σαν παράμετρο ένα
131 # κίνηση 5η: παίξε στο διαθέσιμο τετράγωνο στοιχείο της λίστας και επιστρέφει
132 return board.index(" ") τη θέση του σε αυτή. Αν το στοιχείο
δεν υπάρχει στη λίστα τότε προκύπτει
Απομένει μόνο ν’ αντικαταστήσουμε στο κύριο πρόγραμμα την κλήση σφάλμα.
της randomPosition με μια κλήση στην paperPosition, έτσι ώστε
Εδώ η index χρησιμοποιείται για να
το πρόγραμμά μας να παίζει ακολουθώντας πλέον τις οδηγίες του αναζητηθεί το κενό " " στη λίστα
έξυπνου χαρτιού. board, δηλαδή για να βρεθεί η τελευ-
153 if player == computer: ταία διαθέσιμη θέση.
154 # επιλογή θέσης από το πρόγραμμα
155 position = paperPosition(board)
oxo/src/oxo.7.py
ΤΡΙΛΙΖΑ 20

Τροποποιήσεις – Επεκτάσεις
5.1 Η συνάρτηση readPosition ξεκινά εμφανίζοντας στον παίκτη την τρέ-
χουσα κατάσταση του πίνακα του παιχνιδιού:
# εμφάνιση πίνακα τρίλιζας και πιθανών κινήσεων
print3x3(board)
print3x3(range(9))
Αντικαταστήστε την τελευταία από τις εντολές, όπως φαίνεται στον κώ-
δικα που ακολουθεί:
# εμφάνιση πίνακα τρίλιζας και πιθανών κινήσεων
print3x3(board)
p = [position if board[position] == " " else "."
for position in range(9)]
print3x3(p)
Εκτελέστε το πρόγραμμα και παρατηρήστε τι συμβαίνει. Τί ακριβώς πε-
ριέχει η λίστα p;
oxo/exercises/oxo.8.py

5.2 Όταν θελήσαμε να τροποποιήσουμε το πρόγραμμα ώστε να εμφανίζει το


νικητή, διαπιστώσαμε ότι στο τέλος του παιχνιδιού η μεταβλητή player
δεν αντιστοιχεί στον παίκτη που κέρδισε, αλλά στον ηττημένο. Αυτό
οφείλεται στο γεγονός ότι η τιμή της player εναλλάσσεται στο τέλος
της επανάληψης.
Μετακινήστε στην αρχή της επανάληψης τις εντολές που εναλλάσ-
σουν την τιμή της player και κάντε τις απαραίτητες τροποποιήσεις ώστε
το πρόγραμμα να λειτουργεί και πάλι σωστά.
oxo/exercises/oxo-playermod.py

5.3 Το έξυπνο χαρτί ξεκινά πάντα παίζοντας με τα Χ σε γωνιακό τετράγωνο.


Αν ο παίκτης με τα Ο απαντήσει παίζοντας σ’ ένα πλευρικό τετράγωνο
(θέσεις 1, 3, 5 και 7, σύμφωνα με την αρίθμησή μας) τότε το έξυπνο χαρτί
θα επιλέξει τη γωνία που βρίσκεται διαγωνίως απέναντι από την αρχική
(σχήμα 5.6). Αυτή η κίνηση καταλήγει σε ισοπαλία, ενώ υπάρχει καλύ-
τερη που οδηγεί με βεβαιότητα τον παίκτη με τα Χ στη νίκη.
Να επεκτείνετε τις οδηγίες του έξυπνου χαρτιού έτσι ώστε να μην πα-
ρουσιάζουν αυτή την “αδυναμία”. Όταν ο παίκτης με τα Ο ξεκινήσει παί- Σχήμα 5.6: Η 2η κίνηση του έξυπνου
ζοντας σε πλευρικό τετράγωνο, οι οδηγίες θα πρέπει να επιλέγουν την χαρτιού (κάτω δεξιά) αν ο παίκτης με
τα Ο παίξει σε πλευρικό τετράγωνο.
κατάλληλη κίνηση ώστε ο παίκτης με τα Χ να κερδίζει. Στη συνέχεια, Αυτή η κίνηση καταλήγει σε ισοπα-
να επεκτείνετε ανάλογα και τη συνάρτηση paperPosition. λία, ενώ υπάρχει καλύτερη που οδηγεί
αυτόματα στη νίκη.
Αν προτιμάτε να βρείτε έτοιμες τις τροποποιημένες οδηγίες, ώστε να εστιά-
σετε μόνο στην υλοποίησή τους, μπορείτε να ανατρέξετε στις επεκτάσεις
του έξυπνου χαρτιού: pythonies.mysch.gr/ipaper-ext.pdf.
oxo/exercises/oxo-more-intelligent.py

5.4 Όταν φτάσαμε στο σημείο όπου το πρόγραμμά μας έπρεπε να αναλάβει το
ρόλο ενός εκ των δύο παικτών, ήταν απαραίτητο να διαθέτουμε έναν μη-
χανισμό ο οποίος θα επέτρεπε στο πρόγραμμα να ελέγχει αν ένας παίκτης
ΤΡΙΛΙΖΑ 21

έχει τη δυνατότητα να κάνει τρίλιζα. Για τον σκοπό αυτό, αναπτύξαμε τη


συνάρτηση checkPartial.
Στο βιβλίο του Invent your own computer games with Python, ο Al
Sweigart αφιερώνει επίσης ένα κεφάλαιο στην τρίλιζα και προτείνει τον
εξής τρόπο για να ελέγχεται αν ένας παίκτης έχει τη δυνατότητα να κά-
νει τρίλιζα: για κάθε διαθέσιμη θέση δημιουργείται ένα αντίγραφο του Για να δημιουργήσουμε ένα αντί-
γραφο μιας λίστας, μπορούμε να χρη-
πίνακα του παιχνιδιού, συμπληρώνεται η θέση με το σύμβολο του παίκτη
σιμοποιήσουμε τη μέθοδο copy(), η
και ελέγχεται αν έχει γίνει τρίλιζα. Ουσιαστικά, το πρόγραμμα δοκιμά- οποία εφαρμόζεται σε μια λίστα κι
ζει τι θα συμβεί στην επόμενη κίνηση. επιστρέφει ένα αντίγραφό της.

Να αναπτύξετε μια νέα υλοποίηση της checkPartial που να χρησιμο-


ποιεί τη μέθοδο που περιγράψαμε για να ελέγξει αν ένας παίκτης έχει τη
δυνατότητα να κάνει τρίλιζα. Για να ελέγχει η συνάρτησή σας αν μια κί-
νηση έχει οδηγήσει σε τρίλιζα, θα πρέπει να καλεί τη συνάρτηση check.
oxo/exercises/oxo-lookahead.py

5.5 Μια αρίθμηση των τετραγώνων της τρίλιζας που πιθανώς να βόλευε
περισσότερο τους παίκτες είναι αυτή που φαίνεται στο σχήμα 5.7. Αυτή 7 8 9
η αρίθμηση αντιστοιχεί στη διάταξη που έχουν τα αριθμητικά πλήκτρα
σε ένα τηλέφωνο ή στο πληκτρολόγιο. 4 5 6
Τροποποιήστε το πρόγραμμα που αναπτύχθηκε έτσι ώστε ο παίκτης να
επιλέγει το τετράγωνο στο οποίο θα παίζει με βάση αυτή την αρίθμηση. 1 2 3
Μια πιθανή προσέγγιση είναι να τροποποιηθεί και η εσωτερική αναπαρά-
σταση, ώστε να υπάρχει μια φυσικότερη αντιστοιχία με την αρίθμηση που Σχήμα 5.7: Μια διαφορετική αρίθ-
αντιλαμβάνεται ο παίκτης. μηση των τετραγώνων της τρίλιζας,
με βάση την οποία επιλέγει ο παί-
oxo/exercises/oxo-dial-representation.py
κτης το τετράγωνο στο οποίο θα παί-
Εναλλακτικά, μπορεί η εσωτερική αναπαράσταση να παραμείνει ως έχει ξει. Αυτή η αρίθμηση αντιστοιχεί στο
και να προστεθεί στον κώδικα ένα επίπεδο “αντιστοίχισης” ανάμεσα στην κλασικό αριθμητικό πληκτρολόγιο.
εσωτερική αναπαράσταση και την αρίθμηση από την σκοπιά του παίκτη.
oxo/exercises/oxo-dial-mapping.py

5.6 Το έξυπνο χαρτί παρέχει οδηγίες μόνο για λογαριασμό του παίκτη που
παίζει πρώτος. Προσπαθήστε να γράψετε αντίστοιχες οδηγίες για τον
παίκτη που παίζει δεύτερος και να τις υλοποιήσετε σε μια συνάρτηση
αντίστοιχη της paperPosition.
Αν προτιμάτε να βρείτε έτοιμες τις οδηγίες για τον δεύτερο παίκτη, ώστε
να εστιάσετε μόνο στην υλοποίησή τους, μπορείτε να ανατρέξετε στις επε-
κτάσεις του έξυπνου χαρτιού: pythonies.mysch.gr/ipaper-ext.pdf.
Για να χρησιμοποιήσετε τη συνάρτησή σας, θα χρειαστεί να κάνετε τις
απαραίτητες τροποποιήσεις ώστε το πρόγραμμα να μπορεί να αναλάβει
το ρόλο οποιουδήποτε από τους δύο παίκτες.
oxo/exercises/oxo-twoplayer.py

Pythonies - Τρίλιζα
pythonies.mysch.gr/chapters/oxo.pdf
Γιώργος Μπουκέας, Βασίλης Βασιλάκης (2015-2016) creativecommons.org/licenses/
by-sa/4.0/deed.el
Το παρόν υλικό διατίθεται με άδεια Creative Commons BY-SA 4.0.

You might also like