-
Notifications
You must be signed in to change notification settings - Fork 1
/
pgn.py
257 lines (214 loc) · 15.9 KB
/
pgn.py
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
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
# -------------------------------------------------------------------------------------------------------------------- #
# pgn.py: περιέχει την κλάση FilePGN για επεξεργασία ενός αρχείου .pgn #
# -------------------------------------------------------------------------------------------------------------------- #
class FilePGN:
"""
Δέχεται σαν όρισμα τη διεύθυνση ενός αρχείου pgn
Με τις μεθόδους αποσπά από το αρχείο τις πληροφορίες που περιέχει, τις επεξεργάζεται και τις οργανώνει σε δομές
...
Ορίσματα:
file_path (str):
διεύθυνση αρχείου pgn
Μέθοδοι:
get_info(self, game_no: int =0) -> dict:
δημιουργεί και επιστρέφει λεξικό με τις πληροφορίες ενός συγκεκριμένου αγώνα
__split_files(self) -> list[str]:
δημιουργεί και επιστρέφει λίστα με τις πληροφορίες και κινήσεις των παιχνιδιών που περιέχει το αρχείο pgn
__get_index_of_games(self) -> list:
επιστρέφει λίστα με τις θέσεις των παιχνιδιών της λίστας από split_files
@staticmethod
__get_moves_as_list(game_moves: str) -> list[str]:
επιστρέφει λίστα με τις επεξεργασμένες κινήσεις του αγώνα
@staticmethod
__get_total_rounds(processed_game_moves: list) -> str:
επιστρέφει συμβολοσειρά με τον αριθμό των γύρων του παιχνιδιού
"""
def __init__(self, file_path: str):
"""
Μέθοδος για αρχικοποίηση αντικειμένου της κλάσης
...
Ορίσματα:
file_path (str): διεύθυνση αρχείου pgn που θα χρησιμοποιηθεί από το αντικείμενο
"""
# η κλάση παίρνει σαν όρισμα τη διεύθυνση ενός αρχείου *.pgn
self.file_path = file_path
# κλήση συνάρτησης "split_files" για το αρχείο αυτό
# η λίστα self.game_data πλέον περιλαμβάνει
# α) στις θέσεις n την πληροφορία ενός από τους αγώνες και
# β) στις θέσεις n+1 τις κινήσεις του εκάστοτε αγώνα
# [όπου "n" ζυγός μη αρνητικός αριθμός (0, 2, 4 κλπ)]
self.game_data = self.__split_files()
# λίστα με τις θέσεις όπου βρίσκονται πληροφορίες αγώνα
self.index_of_games = self.__get_index_of_games()
def get_info(self, game_no: int = 0) -> dict[str, str | list]:
"""
Εισάγει σε λεξικό τις πληροφορίες ενός αγώνα από το αρχείο .pgn και το επιστρέφει.
Λέξεις κλειδιά λεξικού: Event, Site, Date, White, Black, Result, Rounds, moves
Ορίσματα:
---------
game_no (int) default=0:
ζυγός μη αρνητικός αριθμός (0, 2, 4 κλπ) που δείχνει τον αγώνα που θα αναλυθεί
Επιστρεφόμενο αντικείμενο:
--------------------------
game_dict (dict):
λεξικό με τις πληροφορίες ενός αγώνα
"""
# συνάρτηση που επιστρέφει την πληροφορίες από τις λέξεις κλειδιά της info_list
info_list = ["Event ", "Site ", "Date ", "Round ", "White ", "Black ", "Result "]
# αρχικοποίηση λεξικού που θα περιέχει τις πληροφορίες του αγώνα
game_dict = {}
# δημιουργία λίστας με συμβολοσειρές, που περιέχει τις πληροφορίες του αγώνα που επιλέχθηκε
game_info = self.game_data[game_no].split("\n")
# εξαγωγή πληροφοριών αγώνα
# για κάθε λέξη-κλειδί της info_list
for key_word in info_list:
# για κάθε συμβολοσειρά της game_info
for string in game_info:
# πχ key_word: Event , string: [Event "Sparkassen Chess Meeting"]
if string[1: 1 + len(key_word)] == key_word:
# εύρεση θέσης που ξεκινάει και τελειώνει η πραγματική πληροφορία που μας ενδιαφέρει
start = string.find("\"") + 1
end = string.rfind("\"")
# προσθήκη σε λεξικό
game_dict[key_word.strip()] = string[start:end]
break
# διατρέξαμε όλες τις συμβολοσειρές και δε βρέθηκε η σχετική πληροφορία
else:
game_dict[key_word.strip()] = "[no info]"
try:
# δημιουργία συμβολοσειράς, που περιέχει τις κινήσεις του αγώνα
game_moves = self.game_data[game_no + 1]
except IndexError:
game_moves = ""
# προσθήκη λίστας κινήσεων στο λεξικό
game_dict["moves"] = self.__get_moves_as_list(game_moves)
# προσθήκη συνολικών γύρων στο λεξικό
game_dict["RoundsPlayed"] = self.__get_total_rounds(game_dict["moves"])
# επιστρεφόμενη τιμή: λεξικό με όλες τις πληροφορίες του αγώνα
return game_dict
def __split_files(self) -> list[str]:
"""
Εξάγει και επιστρέφει αγώνες από ένα αρχείο pgn
Αποθηκεύει τις πληροφορίες του αγώνα σε μία λίστα με:
α) στις θέσεις n την πληροφορία ενός από τους αγώνες και
β) στις θέσεις n+1 τις κινήσεις του εκάστοτε αγώνα
[όπου "n" ζυγός μη αρνητικός αριθμός (0, 2, 4 κλπ)]
Επιστρεφόμενο αντικείμενο:
game_data_list (list[str]):
λίστα με συμβολοσειρές με τις πληροφορίες που αποσπάστηκαν από το αρχείο
"""
# διάβασμα του αρχείου pgn
with open(self.file_path, "r") as pgn:
# αρχικοποίηση λίστας αποθήκευσης πληροφοριών
game_data_list = []
# αρχικοποίηση βοηθητικής συμβολοσειράς όπου θα αποθηκεύεται προσωρινά η πληροφορία
game_data = ''
# προσπέλαση αρχείου pgn γραμμή προς γραμμή
for line in pgn:
# προσθήκη γραμμής βοηθητική συμβολοσειρά
game_data += line
if line == "\n":
# κάθε φορά που γίνεται διάβασμα κενής γραμμής, σημαίνει ότι ολοκληρώθηκε το διάβασμα
# είτε των στοιχείων ενός αγώνα είτε των κινήσεων του
if game_data == "\n":
# σε περίπτωση που σε κάποιο σημείο υπάρχουν περισσότερες από μία κενές γραμμές, η game_data θα
# περιλαμβάνει μόνο τον χαρακτήρα αλλαγής γραμμής·
# την αγνοώ και η μέθοδος συνεχίζει στην επόμενη γραμμή
game_data = ''
continue
# όση πληροφορία διαβάστηκε προστίθεται στη λίστα
game_data_list.append(game_data)
# και η βοηθητική συμβολοσειρά αδειάζει
game_data = ''
# προσθήκη τελευταίας σειράς κινήσεων, σε περίπτωση που το τελικό αρχείο
# δεν τελειώνει με μία κενή σειρά
if game_data:
game_data_list.append(game_data)
# η επιστρεφόμενη λίστα περιέχει
# α) στις θέσεις n την πληροφορία ενός από τους αγώνες και
# β) στις θέσεις n+1 τις κινήσεις του εκάστοτε αγώνα
# [όπου "n" ζυγός μη αρνητικός αριθμός (0, 2, 4 κλπ)]
pgn.close()
return game_data_list
def __get_index_of_games(self) -> list:
"""
Επιστρέφει λίστα με τα index των πληροφοριών παιχνιδιών ενός αρχείου pgn
Επιστρεφόμενο αντικείμενο:
index_of_games (list):
λίστα με τα index των πληροφοριών παιχνιδιών ενός αρχείου pgn
"""
index_of_games = []
# αποθήκευση σημείων όπου ξεκινάει ο κάθε αγώνας
for num in range(0, len(self.game_data), 2):
index_of_games.append(num)
return index_of_games
@staticmethod
def __get_moves_as_list(game_moves: str) -> list[str]:
"""
Δέχεται σαν όρισμα συμβολοσειρά με κινήσεις από το αρχείο .pgn και εξάγει τις κινήσεις σε επιστρεφόμενη λίστα
Η λίστα περιέχει συμβολοσειρές με την κάθε κίνηση αυτούσια
Η συνάρτηση επίσης αφαιρεί σχόλια, εάν βρεθούν
Ορίσματα:
---------
game_moves (str): κινήσεις συγκεκριμένου αγώνα (προ επεξεργασίας)
Επιστρεφόμενο αντικείμενο:
--------------------------
(list): λίστα με κινήσεις (πλην τελευταίου στοιχείου που είναι το αποτέλεσμα)
"""
if not game_moves:
return []
# έλεγχος εάν υπάρχουν σχόλια μέσα στις κινήσεις
while True:
# αναζήτηση θέσης αγκύλης αρχής σχολίου
comment_start = game_moves.find("{")
# εάν δε βρεθεί κάποιο σχόλιο, γίνεται έξοδος από τον ατέρμον βρόγχο
if comment_start == -1:
break
# δεν έχει γίνει "break", επομένως βρέθηκε αγκύλη έναρξης σχολίου
# οπότε γίνεται αναζήτηση θέσης αγκύλης τέλους σχολίου
comment_end = game_moves.find("}") + 1
# το σχόλιο αφαιρείται από τη συμβολοσειρά με τις κινήσεις
game_moves = game_moves[:comment_start] + game_moves[comment_end:]
# ο ατέρμον βρόγχος ξαναδιαβάζει τη νέα συμβολοσειρά για να βρει το επόμενο σχόλιο (εάν υπάρχει)
# χωρίζουμε τις κινήσεις με βάση τα κενά και αλλαγές γραμμής
moves_list = game_moves.split()
# διατρέχουμε τη λίστα με τς κινήσεις
for item in moves_list:
# εάν η τελεία βρίσκεται μέσα στην κίνηση, σημαίνει ότι πρόκειται για δείκτη γύρου
# και θα παραληφθεί
if "." in item:
# προχωράμε σε έλεγχο εάν το αρχείο pgn έχει κενό μετά το δείκτη γύρου ή όχι
if item[-1] == ".":
# εάν η τελεία βρίσκεται στο τέλος της συμβολοσειράς, πρόκειται για δείκτη γύρου και αφαιρείται
# π.χ. "1."
moves_list.remove(item)
else:
# επειδή η συμβολοσειρά περιέχει την τελεία "." αλλά όχι στην τελευταία θέση, έχουμε δείκτη γύρου
# κολλητά με κάποια κίνηση (π.χ. 1.e4 αντί για 1. e4)
# βρίσκω τη θέση όπου βρίσκεται η συμβολοσειρά με την κίνηση μέσα στη λίστα (θα χρειαστεί παρακάτω
# ώστε να προσθέσουμε στο ίδιο σημείο την επεξεργασμένη κίνηση και να μην αλλάξουμε τη σειρά)
index = moves_list.index(item)
# η συμβολοσειρά χωρίζεται με βάση τον χαρακτήρα της τελείας (π.χ. 1.e4 θα γίνει ["1.", "e4"])
split_move = item.split(".")
# κρατάω το δεξί μέρος της λίστας που επιστράφηκε (split_move[1] == "e4") και την εισάγω στο σωστό
# σημείο μέσα στην αλληλουχία των κινήσεων, ώστε να μην τροποποιηθεί η σειρά
# (αντικαθίσταται η παλιά κίνηση "1.e4" με την επεξεργασμένη "e4")
moves_list[index] = split_move[1]
# επιστρεφόμενη τιμή: λίστα με κινήσεις (πλην τελευταίου στοιχείου που είναι το αποτέλεσμα)
return moves_list[:len(moves_list) - 1]
@staticmethod
def __get_total_rounds(processed_game_moves: list) -> str:
"""
Επιστρέφει συμβολοσειρά με τον αριθμό των γύρων του παιχνιδιού
Λαμβάνει σαν όρισμα λίστα με επεξεργασμένες κινήσεις του αγώνα και επιστρέφει συμβολοσειρά με τον τελευταίο γύρο
Ορίσματα:
---------
game_moves (list): επεξεργασμένες κινήσεις συγκεκριμένου αγώνα
Επιστρεφόμενο αντικείμενο:
--------------------------
(str): συμβολοσειρά με τον αριθμό των γύρων του αγώνα
"""
# αποθήκευση μήκους λίστας κινήσεων για ακέραια διαίρεση
length = len(processed_game_moves)
# επιστρεφόμενη τιμή είναι συμβολοσειρά με τον αριθμό γύρων του αγώνα
return str(length // 2 if length % 2 == 0 else (length // 2) + 1)