-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathREADME_di.txt
130 lines (99 loc) · 17.1 KB
/
README_di.txt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
Σέργιος - Ανέστης Κεφαλίδης
Εντολή μεταγλώττισης: make (υπάρχουν και κάποιοι παράμετροι, TESTS=Y για τα unit tests, GUI=Y για την χρηση της γραφικής αναπαράστασης,
PLATFORM=WINDOWS για compilation για Windows)
Όνομα εκτελέσιμου: l5r
Εισαγωγή:
Το συγκεκριμένο πρότζεκτ μου κέντρισε το ενδιαφέρον για αυτό και εκτός από αυτά που απαιτούνται από την εκφώνηση υλοποίησα και
κάποιες άλλες λειτουργίες:
1. Υλοποίησα μια εφαρμογή, με τη χρήση της βιβλιοθήκης Qt, η οποία αναπαριστά την τρέχουσα κατάσταση του παιχνιδιού με
γραφικά. Σε αυτό ήταν πολύτιμη και υψίστης σημασίας η βοήθεια του Παναγιώτη Κιναλή ο οποίος σχεδίασε τις κάρτες. Την εφαρμογή αυτή
μπορείτε να τη βρείτε στο GitHub μου (https://github.com/DangWang), σκοπεύω να τη κάνω δημόσια όταν τελειώσει η προθεσμία.
2. Βρίσκομαι στη διαδικασία (και κατά πάσα πιθανότητα έχω ολοκληρώσει ανάλογα με το πότε διαβάζετε αυτό το κείμενο) προσθήκης
διαδικτυακής λειτουργικότητας στο πρόγραμμα. Χρησιμοποιώ τα sockets/winsock APIs για Linux και Windows αντίστοιχα. Αν έχει τελειώσει
επίσης θα μπορείτε να το βρείτε στο GitHub μου.
Όλα αυτά σας τα λέω για να μην παραξενευτείτε αν δείτε κάποια πράγματα τα οποία δεν θα χρειαζότουσαν για την εργασία όπως τη
δυνατότητα να εκτελεστεί σε περιβάλλον Windows, τον κώδικα που χρησιμοποιείται για να τρέξει η εφαρμογή στο Qt και ότι άλλο.
Όσον αφορά τους κανόνες έχω κάνει μερικές μικρές αλλαγές (στη μάχη κυρίως) της οποίες μπορείτε να βρείτε στο αρχείο με τους κανόνες επειδή οι
κανόνες που περιγράφονται στην εργασία είναι προϊόν τέτοιας απλοποίησης που το παιχνίδι δεν βγάζει πλέον νόημα και μιας και η
εκφώνηση αφήνει τα περιθώρια.
Κώδικας:
Τι περιέχεται σε κάθε αρχείο:
DeckBuilder/Typeconverter: Γνωρίζετε τη λειτουργία τους, διόρθωσα όσα λάθη υπήρχαν και έβγαλα κάποια includes που δεν
χρειαζόντουσαν.
main.cpp: Αρχικοποίηση γεννήτριας τυχαίων αριθμών, δημιουργία και αρχικοποίηση του GameManager, καλεί ότι χρειάζεται για να
αρχίσει το παιχνίδι.
game_manager.cpp/h: Περιέχει τη κλάσση GameManager της οποίας όλα τα μέλη είναι static. Αυτή η κλάσση ελέγχει το παιχνίδι και
περιέχει πληροφορίες που περιγράφουν το παιχνίδι (όπως το πλήθος των παικτών, ποιός παίκτης παίζει τώρα). Ουσιαστικά η main περνάει
σε αυτή την κλάσση τον έλεγχο για να τρέξει το παιχνίδι.
player.cpp/h: Περιέχει την κλάσση Player η οποία έχει και το μεγαλύτερο κομμάτι του κώδικα του προγράμματος. Οι δύο σημαντικότερες
συναρτήσεις είναι η GetInput() η οποία χειρίζεται την είσοδο του χρήστη και η StartTurn(GamePhase) στην οποία περιέχεται η λογική
του κάθε γύρου.
province.cpp/h: Περιέχει την κλάσση Province, την επαρχία. Επειδή ο κώδικας της μάχης είναι μικρός, περιέχεται σε αυτή την κλάσση (SimulateBAttle).
Όσο συνεχίζω να δουλεύω στο παιχνίδι πιστεύω οτι θα χρειαστεί να μεταφέρω τον κώδικα της μάχης σε μια ξεχωριστή κλάσση.
card.cpp/h: Περιέχει την abstract κλάσση Card και ότι λειτουργίες χρειάζονται για όλες τις κάρτες.
black_card.cpp/h: Περιέχει την κλάσση BlackCard.
green_card.cpp/h: Περιέχει την κλάσση GreenCard.
follower.cpp/h, item.cpp/h, holding.cpp/h, personality.cpp/h: Περιέχουν τις αντίστοιχες κλάσσεις και πληροφορίες. Όμως δεν
περιλαμβάνουν πληροφορίες για την κάθε κάρτα ξεχωριστά, υπάρχει ξεχωριστό αρχείο για κάθε κάρτα.
Followers/*: Υπάρχει ένα ζευργάρι πηγαίου/επικεφαλίδας αρχείων για κάθε κάρτα. Εκτός από αυτά υπάρχει και το αρχέιο followers.h
το οποίο κάνει include όλες τις επικεφαλίδες που υπάρχουν σε αυτό τον φάκελο.
Holdings/*, Items/*, Personalities/*: Αντίστοιχα.
/Holdings/Stronghold.cpp/h: Περιέχει και τη κλάσση Stronghold και τις 3 υποκλάσσεις που αντιπροσωπεύουν τα διαφορετικά φρούρια. Είναι
ασυνεπής με τη προηγούμενη επιλογή του να υπάρχει ξεχωριστό αρχείο για κάθε κάρτα, αλλά σκέφτηκα σχετικά μετά το να φτιάξω 3 φρούρια,
ο κώδικας ήταν λίγος και έτσι τα άφησα εκεί πέρα. Αν βρω χρόνο θα τα αλλάξω (οπότε αυτό που διαβάζετε μπορεί να μην ισχύει).
printer.cpp/h: Η κλάσση Printer υπάρχει για να διευκολύνει την οριζόντια εκτύπωση των καρτών. Επειδή το να εκτυπώνεις την κάρτα
τη μία κάτω από την άλλη στο τερματικό μπορεί να είναι οκ, αλλά πρακτικά δεν είναι βιώσιμο, έφτιαξα αυτή τη κλάσση και την αντίστοιχη
λογική για να εκτυπώνω οριζόντια. Επίσης κοιτάει το οριζόντιο μέγεθος του τερματικού και εκτυπώνει ανάλογα, το κοιτάει όμως μόνο στην
αρχή οπότε καλό θα ήταν να βάλετε το τερματικό στο μέγεθος που θέλετε και μετά να αρχίσετε το πρόγραμμα. Όλα τα μέλη είναι static.
terminal_colours.h: Περιέχει μερικά macros για να εκτυπώνονται χρώματα στο τερματικό. Έχω συμπεριλάβει macros και για Linux και
για Windows.
../image_files_enum.h: Χρησιμοποιείται για το GUI.
Αρχικά θα σχολιάσω ότι οι static classes (GameManager, Printer) θα μπορούσαν να υλοποιηθούν και ως namespace αλλά προτίμησα αυτό
επειδή έχουν και μεταβλητές, άρα μου κάνουν πιο ωραία σαν κλάσσεις (και επειδή δεν είχα σκεφτεί ότι γίνεται το άλλο για αρκετό καιρό).
Τι γίνεται, πώς δουλεύουν τα πράγματα:
Γενικά: 1. Προτιμώ τα vectors αντί για lists. Lists χρησιμοποιώ μόνο για τις τράπουλες των παικτών (όπως και ο κώδικας που μας δόθηκε).
2. Προσπάθησα να περιορίσω τη χρήση των macros όσο γίνεται, αλλά σε κάποια σημεία ήταν αρκετά βολικά (βλέπε Player:OptimizeChains).
3. Κάνω χρήση δυνατοτήτων της C++11 (auto, range based for loops, nullptr).
Κάρτες: Το πώς λειτουργεί το μεγαλύτερο μέρος είναι προφανές. Η συνεισφορά των καρτών στο παιχνίδι από πλευράς λογικής είναι ελάχιστη.
Το πως εκτυπώνονται είναι το μόνο ενδιαφέρον. Κάθε κάρτα στο τερματικό περιβάλλεται από ένα πλαίσιο (χαρακτήρες '-', '|') και μέσα
σε αυτό υπάρχουν πληροφορίες για την κάρτα. Για να γίνει αυτό η Card περιέχει μια μέθοδο που εκτυπώνει την οριζόντια γραμμή του πάνω
μέρους. Μετά εκτυπώνεις όσες πληροφορίες περιέχει. Ύστερα η κάθε υποκλάσση της συμπληρώνει πληροφορίες, και αυτό συνεχίζει μέχρι το τελευταίο
επίπεδο (π.χ. Bow, Mine...) όπου αφού εκτυπωθούν οι πληροφορίες, εκτυπώνεται ξανά μια οριζόντια γραμμή για να κλείσει η κάρτα.
Ακόμα πιο ενδιαφέρουσα είναι η οριζόντια εκτύπωση, με τη συμβολή της Printer. Κάθε κάρτα αντί να εκτυπώνει όπως πριν, προσθέτει
στο buffer της Printer την αντίστοιχη πληροφορία (για να μπορεί να γίνει αυτό πρέπει αρχικά να περαστεί το "ύψος" του είδους που θέλουμε
να εκτυπώσουμε οριζόντια). Σημαντική διαφορά είναι ότι δεν προστίθεται το | της αρχής, αυτό το εκτυπώνει η Printer όταν πρέπει 1*.
Αφου έχουν περαστεί όλες οι πληροφορίες που θέλουμε στην Printer, καλούμε την εκτύπωση και αυτή γίνετια όπως περιγράφεται στο αρχείο.
Όσον αφορά τη συνεισφορά των καρτών στο παιχνίδι, έκανα μερικές αλλαγές ώστε ο κάθε παίκτης να έχει δικό του DeckBuilder για τις κάρτες του
το οποίο φροντίζει και για να καθαρίσει τη μνήμη που δεσμεύεται για τη δημιουργία των τραπουλών. Πέρα από αυτό δεν νομίζω ότι υπάρχει
κάτι άλλο που να είναι παράξενο.
Το άλλο ενδιαφέρον είναι η βελτιστοποίηση των αλυσιδών. Κάθε Holding έχει ένα string που είναι ο τύπος του upperHolding και έναν
δείκτη σε ένα Holding για το upperHolding. Αντίστοιχα για το subHolding. Υπάρχουν επίσης σε κάθε holding macros που ορίζουν τους τύπους
και τις αξίες διασύνδεσης των holdings. Φυσικά αν κάποιο holding δεν μπορεί να δημιουργήσει αλυσίδα υπάρχει αντίστοιχος τυπος None.
Ύστερα κάθε φορά που ο παίκτης αγοράζει μία καινούργια ιδιοκτησία το πρόγραμμα προσπαθεί να βελτιστοποιήσει τις υπάρχουσες αλυσίδες
ξαναφτιάχνοντας τις αλυσίδες από την αρχή. Το βέλτιστο είναι να κοιτάει πρώτα αν η νέα ιδιοκτησία μπορεί να αποτελέσει μέρος αλυσίδας
αλλά επέλεξα την πιο απλή επιλογή για να μην δημιουργηθούν απρόβλεπτα σφάλματα (έτσι και αλλιώς η ταχύτητα δεν είναι πρωτεραιότητα
στο συγκεκριμένο παιχνίδι).
Παίκτης: Η κλάσση Player αποτελείται κατά κύριο λόγο από απλές και προβλέψιμες συναρτήσεις. Οι δύο συναρτήσεις που χαίρουν προσοχής
είναι η StartTurn και η GetInput. Στη StartTurn περιέχεται το βασικό μέρος της λογικής του γύρου. Σε ένα πιο κλασσικό μοντέλο
δημιουργίας παιχνιδιών θα ήταν ο βασικό βρόγχος. Παίρνει σαν όρισμα την φάση του παιχνιδιού, εκτυπώνει τα αντίστοιχα μηνύματα και δίνει
στον παίκτη την ικανότητα να πραγματοποιήσει τον γύρω του. Όποτε η StartTurn χρειάζεται είσοδο από το πληκτρολόγιο καλεί την GetInput.
Η GetInput φροντίζει να δώσει ο χρήστης αποδεκτή είσοδο ώστε να μην κρασάρει το παιχνίδι. Η GetInput ουσιαστικά θα μπορούσε να είναι
κομμάτια διασκορπισμένα στην StartTurn αλλά προτίμησα να τα οργανώσω σε μία συνάρτηση ειδικά αφού έχουν πολλά κοινά χαρακτηριστικά.
Η GetInput κάνει χρήση εξαιρέσεων για να διευκολυνθεί η αντιμετώπιση της εσφαλμένης εισόδου.
Μάχες: Οι μάχες εκτελούνται μετά την ολοκλήρωση της φάσης μάχης. Όταν τελειώσει αυτή η φάση ο GameManager πριν προχωρήσει στον επόμενο
γύρo λέει σε όλες τις επαρχίες όλων των παικτών να προσομοιώσουν τις μάχες. Η κάθε επαρχία βλέπει αν εχει μονάδες που να επιτήθενται
και αν έχει προσομοιώνει τη μάχη. Οι λεπτομέρειες παρουσιάζονται στον κώδικα.
Tests: Επειδή υπάρχει αρκετός κώδικας έφτιαξα μερικά tests χρησιμοποιώντας τη βιβλιοθήκη Catch2. Για αυτό είμαι αρκετά βέβαιος ότι
ένα πολύ μεγάλο κομμάτι κώδικα δουλεύει. Μπορείτε και εσείς να τρέξετε τα tests. Ήταν ιδιατέρως χρήσιμα στην διόρθωση των memory leaks.
Όσον αφορά την εφαρμογή σε Qt: Αρχικά σκόπευα να φτιάξω ένα AppImage (ή κάποιο άλλο πακέτο με όλα τα dependencies) αλλά αυτό τελικά
είναι δύσκολο μιας και δεν υπάρχει κάποιο πρόγραμμα που να υποστηρίζει σύγχρονα Linux για να φτιάξω κάτι τέτοιο, και δεν θα κάτσω να βάλω
Ubuntu 14.04/CentOS 7 για αυτή τη δουλειά. Για αυτό το λόγο θα σας δώσω οδηγίες για να τρέξετε την εφαρμογή αν έχετε όρεξη να δείτε
κάτι παραπάνω, έχει original artwork από έναν φίλο μου οπότε πιστεύω ότι αξίζει τον κόπο. Έχω δοκιμάσει, κάνει compile απο Qt 9 μέχρι 14.
1. πηγαίνετε στον φάκελο qt_l5r
2. τρέχετε cmake CMakeLists.txt
3. τρέχετε make
4. τρέχετε το make του κυρίου προγράμματος ως εξής make GUI=Y
Μικρές λεπτομέρειες, μπορεί και να έχουν αλλάξει μέχρι να στείλω την εργασία:
1* Αυτό οδηγεί σε μία ασυνέχεια στην εκτύπωσει, επειδή εκτυπωνετα | αντι για - στις οριζόντιες γραμμές. Αυτό μπορεί να διορθωθεί με το να εκτυπώνονται
από την αριστερή άκρη προς τη δεξία στριμωγμένες οι λέξεις, αλλά η εύκολη υλοποίηση το έκανε αλλιώς και μέχρι στιγμής δεν το έχω διορθώσει.
Δεν είναι όμως κάτι φοβερό απλά επιλέγω να αφιερώσω χρόνο σε άλλες λειτουργιες.