-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathclasses.py
More file actions
568 lines (470 loc) · 26 KB
/
classes.py
File metadata and controls
568 lines (470 loc) · 26 KB
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
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
# -*- coding: utf-8 -*-
"""
classes.py
==========
Αρχείο κλάσεων για την εργασία Python Scrabble (ΘΜΕΛ 2026).
Περιλαμβάνει τις 5 κλάσεις του παιχνιδιού (Game, SakClass, Player,
Human, Computer) και τη συνάρτηση documentation() με την τεκμηρίωση
της εργασίας.
"""
import random
import itertools
import json
import os
# ---------------------------------------------------------------------------
# ΣΥΝΑΡΤΗΣΗ ΤΕΚΜΗΡΙΩΣΗΣ (απαιτείται από την εκφώνηση - ενότητα 6.1/6.2)
# ---------------------------------------------------------------------------
def documentation():
"""
- Κλάσεις: Game, SakClass, Player, Human, Computer (5 κλάσεις).
Επιπλέον υλοποιείται η Mediator (απλό μοτίβο διαμεσολαβητή) ως
ξεχωριστή κλάση που χρησιμοποιείται από την Game για τον
συντονισμό της επικοινωνίας μεταξύ των κλάσεων.
- Κληρονομικότητα: Η Player είναι η βασική κλάση. Οι Human και
Computer κληρονομούν από την Player (single inheritance).
Στον constructor τους καλούν super().__init__(...) για να
αρχικοποιήσουν τα κοινά πεδία (όνομα, σκορ, γράμματα).
- Επέκταση μεθόδων: Οι μέθοδοι play() και __repr__() ορίζονται
στην Player και επεκτείνονται (override) στις Human και
Computer. Η Player.play() είναι «αφηρημένη» (επιστρέφει None)
και κάθε υποκλάση δίνει τη δική της λογική. Έτσι επιτυγχάνεται
πολυμορφισμός: η Game καλεί player.play() χωρίς να ξέρει αν
ο παίκτης είναι Human ή Computer.
- Υπερφόρτωση τελεστών: Η Player ορίζει τους τελεστές
__eq__, __lt__, __gt__ ώστε δύο παίκτες να συγκρίνονται με
βάση το σκορ τους (π.χ. p1 > p2). Επίσης ορίζεται __len__
στην SakClass που επιστρέφει το πλήθος γραμμάτων στο σακουλάκι.
- Decorators: Χρησιμοποιείται ο @property στην Player (για το
πεδίο score) και ο @staticmethod στην Computer (για τη
_word_value που υπολογίζει την αξία μιας λέξης).
- Δομή λέξεων: Οι λέξεις της Ελληνικής (greek7.txt) φορτώνονται
σε ένα set (σύνολο). Έτσι ο έλεγχος αν μια λέξη είναι αποδεκτή
γίνεται με τον τελεστή `in` σε χρόνο O(1) (hash lookup), αντί
για O(n) που θα είχε μια list.
- Αλγόριθμος πολιτικής παιχνιδιού: Υλοποιείται ο
ΑΛΓΟΡΙΘΜΟΣ 1: Min-Max-Smart. Στις Ρυθμίσεις του παιχνιδιού ο
παίκτης επιλέγει επίπεδο: 'MIN', 'MAX' ή 'SMART'.
* MIN : επιστρέφει την πρώτη αποδεκτή λέξη ξεκινώντας από
μεταθέσεις 2 γραμμάτων προς 7.
* MAX : επιστρέφει την πρώτη αποδεκτή λέξη ξεκινώντας από
μεταθέσεις 7 γραμμάτων προς 2 (περισσότερα γράμματα).
* SMART: εξαντλεί όλες τις μεταθέσεις 2..7, βρίσκει όλες τις
αποδεκτές λέξεις και επιστρέφει αυτή με το
μεγαλύτερο σκορ.
Οι μεταθέσεις γίνονται με itertools.permutations.
- Πρότυπο Mediator: Η κλάση Mediator κρατάει αναφορές στους
παίκτες, στο σακουλάκι και στο σύνολο των λέξεων. Οι Human
και Computer δεν επικοινωνούν απ' ευθείας μεταξύ τους ούτε
γνωρίζουν την SakClass· καλούν μεθόδους του Mediator
(validate_word, get_letters, put_back_letters, broadcast).
Έτσι η Game χρησιμοποιεί τον Mediator για να συντονίσει το
παιχνίδι, μειώνοντας τις εξαρτήσεις μεταξύ των κλάσεων.
"""
pass
# ---------------------------------------------------------------------------
# ΣΤΑΘΕΡΕΣ ΠΑΙΧΝΙΔΙΟΥ
# ---------------------------------------------------------------------------
# Πλήθος κάθε γράμματος στο "σακουλάκι" του ελληνικού Scrabble.
# Οι τιμές ακολουθούν την επίσημη έκδοση (104 πλακίδια συνολικά,
# χωρίς τα 2 κενά "μπαλαντέρ" πλακίδια που δεν χρησιμοποιούμε στην
# απλοποιημένη εκδοχή της εργασίας -> σύνολο 102 πλακίδια).
LETTER_COUNTS = {
'Α': 12, 'Ο': 9, 'Ε': 8, 'Ι': 8, 'Τ': 8, 'Η': 7, 'Σ': 7, 'Ν': 6,
'Ρ': 5, 'Κ': 4, 'Π': 4, 'Υ': 4,
'Λ': 3, 'Μ': 3, 'Ω': 3,
'Γ': 2, 'Δ': 2,
'Β': 1, 'Φ': 1, 'Χ': 1,
'Ζ': 1, 'Θ': 1, 'Ξ': 1, 'Ψ': 1,
}
# Πόντοι κάθε γράμματος (επίσημες τιμές ελληνικού Scrabble).
LETTER_POINTS = {
# 1 πόντος
'Α': 1, 'Ο': 1, 'Ε': 1, 'Ι': 1, 'Τ': 1, 'Η': 1, 'Σ': 1, 'Ν': 1,
# 2 πόντοι
'Ρ': 2, 'Κ': 2, 'Π': 2, 'Υ': 2,
# 3 πόντοι
'Λ': 3, 'Μ': 3, 'Ω': 3,
# 4 πόντοι
'Γ': 4, 'Δ': 4,
# 8 πόντοι
'Β': 8, 'Φ': 8, 'Χ': 8,
# 10 πόντοι
'Ζ': 10, 'Θ': 10, 'Ξ': 10, 'Ψ': 10,
}
MAX_LETTERS = 7 # Μέγιστος αριθμός γραμμάτων ανά παίκτη
# ---------------------------------------------------------------------------
# ΚΛΑΣΗ: SakClass (το «σακουλάκι» με τα γράμματα)
# ---------------------------------------------------------------------------
class SakClass:
"""Διαχειρίζεται τα πλακίδια-γράμματα του παιχνιδιού."""
def __init__(self):
"""Δημιουργεί κενό σακουλάκι. Καλέστε randomize_sak() για
να γεμίσει και να ανακατευτεί."""
self.letters = [] # Λίστα με τα γράμματα του σακουλιού
self.randomize_sak()
def randomize_sak(self):
"""Γεμίζει το σακουλάκι με όλα τα γράμματα σύμφωνα με το
LETTER_COUNTS και τα ανακατεύει."""
self.letters = []
for letter, count in LETTER_COUNTS.items():
self.letters.extend([letter] * count)
random.shuffle(self.letters)
def get_letters(self, n):
"""Βγάζει από το σακουλάκι έως n γράμματα και τα επιστρέφει
ως λίστα. Αν τα διαθέσιμα είναι λιγότερα, επιστρέφει όσα
υπάρχουν."""
n = min(n, len(self.letters))
taken = self.letters[:n]
self.letters = self.letters[n:]
return taken
def put_back_letters(self, letters):
"""Επιστρέφει μια λίστα γραμμάτων στο σακουλάκι και το
ξανα-ανακατεύει."""
self.letters.extend(letters)
random.shuffle(self.letters)
def is_empty(self):
"""True αν το σακουλάκι έχει αδειάσει."""
return len(self.letters) == 0
def __len__(self):
"""Υπερφόρτωση: len(sak) -> πλήθος γραμμάτων που απομένουν."""
return len(self.letters)
def __repr__(self):
return f"SakClass(remaining={len(self.letters)})"
# ---------------------------------------------------------------------------
# ΒΑΣΙΚΗ ΚΛΑΣΗ: Player
# ---------------------------------------------------------------------------
class Player:
"""Βασική κλάση παίκτη. Οι Human/Computer κληρονομούν από αυτήν."""
def __init__(self, name, mediator):
"""Αρχικοποιεί όνομα, σκορ=0, άδεια λίστα γραμμάτων και
αποθηκεύει αναφορά στον Mediator για επικοινωνία."""
self.name = name
self._score = 0
self.letters = [] # Τα γράμματα του παίκτη
self.mediator = mediator # Πρότυπο Mediator
# --- decorator @property: ασφαλής πρόσβαση στο score ---
@property
def score(self):
"""Επιστρέφει το τρέχον σκορ του παίκτη."""
return self._score
@score.setter
def score(self, value):
if value < 0:
value = 0
self._score = value
def add_score(self, points):
"""Προσθέτει πόντους στο σκορ."""
self._score += points
def refill_letters(self):
"""Συμπληρώνει τα γράμματα του παίκτη ώστε να έχει MAX_LETTERS."""
needed = MAX_LETTERS - len(self.letters)
if needed > 0:
new_letters = self.mediator.get_letters(needed)
self.letters.extend(new_letters)
def remove_letters(self, word):
"""Αφαιρεί από τα γράμματα του παίκτη τα γράμματα της λέξης."""
for ch in word:
if ch in self.letters:
self.letters.remove(ch)
def can_form(self, word):
"""Ελέγχει αν ο παίκτης μπορεί να σχηματίσει τη λέξη με τα
γράμματα που διαθέτει (λαμβάνει υπόψη πολλαπλά εμφανιζόμενα)."""
available = list(self.letters)
for ch in word:
if ch in available:
available.remove(ch)
else:
return False
return True
def play(self):
"""Αφηρημένη μέθοδος. Επεκτείνεται στις Human/Computer.
Πρέπει να επιστρέφει tuple (action, value):
- ('WORD', word) : ο παίκτης παίζει τη λέξη `word`
- ('PASS', None) : ο παίκτης ζητά αλλαγή γραμμάτων
- ('QUIT', None) : ο παίκτης παραιτείται
"""
return ('PASS', None)
# --- Υπερφόρτωση τελεστών (σύγκριση παικτών με βάση το σκορ) ---
def __eq__(self, other):
return isinstance(other, Player) and self._score == other._score
def __lt__(self, other):
return self._score < other._score
def __gt__(self, other):
return self._score > other._score
def __repr__(self):
return f"Player(name={self.name}, score={self._score})"
# ---------------------------------------------------------------------------
# ΠΑΡΑΓΩΓΗ ΚΛΑΣΗ: Human
# ---------------------------------------------------------------------------
class Human(Player):
"""Παίκτης-άνθρωπος. Η play() ζητά είσοδο από το πληκτρολόγιο."""
def __init__(self, name, mediator):
super().__init__(name, mediator)
def play(self):
"""Επεκτείνει την Player.play(). Παρουσιάζει στον άνθρωπο
τα γράμματά του και διαβάζει τη λέξη/εντολή που εισάγει."""
print("*" * 60)
print(f" *** Παίκτης: {self.name} *** Σκορ: {self.score}")
print(f" >>> Γράμματα: {self.letters}")
print()
while True:
user_input = input(" ΛΕΞΗ: ").strip().upper()
# Παραίτηση
if user_input == 'Q':
return ('QUIT', None)
# Αλλαγή γραμμάτων (αγγλικό P = ελληνικό Π)
if user_input == 'P' or user_input == 'Π':
return ('PASS', None)
# Έλεγχος ότι έχει εισαχθεί κάτι
if not user_input:
print(" -> Δώστε λέξη, ή 'P' για αλλαγή, ή 'Q' για έξοδο.")
continue
# Έλεγχος ότι ο παίκτης έχει τα γράμματα
if not self.can_form(user_input):
print(" -> Δεν διαθέτεις τα γράμματα για αυτή τη λέξη.")
continue
# Έλεγχος αν η λέξη είναι αποδεκτή (μέσω Mediator)
if not self.mediator.is_valid_word(user_input):
print(" -> Η λέξη δεν είναι αποδεκτή. Προσπάθησε ξανά.")
continue
# Επιτυχία
return ('WORD', user_input)
def __repr__(self):
return f"Human(name={self.name}, score={self.score})"
# ---------------------------------------------------------------------------
# ΠΑΡΑΓΩΓΗ ΚΛΑΣΗ: Computer
# ---------------------------------------------------------------------------
class Computer(Player):
"""Παίκτης-υπολογιστής. Υλοποιεί τον αλγόριθμο Min-Max-Smart."""
def __init__(self, name, mediator, level='SMART'):
"""level: 'MIN' | 'MAX' | 'SMART' - το επίπεδο δυσκολίας."""
super().__init__(name, mediator)
self.level = level
@staticmethod
def _word_value(word):
"""Decorator @staticmethod: υπολογίζει την αξία μιας λέξης
ως άθροισμα των πόντων των γραμμάτων της."""
return sum(LETTER_POINTS.get(ch, 0) for ch in word)
def _find_word_min(self):
"""MIN: Πρώτη αποδεκτή λέξη, ξεκινώντας από μεταθέσεις 2
γραμμάτων και ανεβαίνοντας προς 7."""
for size in range(2, len(self.letters) + 1):
for perm in itertools.permutations(self.letters, size):
word = ''.join(perm)
if self.mediator.is_valid_word(word):
return word
return None
def _find_word_max(self):
"""MAX: Πρώτη αποδεκτή λέξη, ξεκινώντας από μεταθέσεις
max γραμμάτων και κατεβαίνοντας προς 2."""
for size in range(len(self.letters), 1, -1):
for perm in itertools.permutations(self.letters, size):
word = ''.join(perm)
if self.mediator.is_valid_word(word):
return word
return None
def _find_word_smart(self):
"""SMART: Όλες οι αποδεκτές λέξεις 2..max γραμμάτων, και
επιλογή αυτής με το μεγαλύτερο σκορ."""
best_word = None
best_value = -1
# Χρησιμοποιούμε set για να μην ελέγχουμε την ίδια λέξη πολλές φορές
# (οι μεταθέσεις παράγουν διπλότυπα όταν υπάρχουν ίδια γράμματα).
seen = set()
for size in range(2, len(self.letters) + 1):
for perm in itertools.permutations(self.letters, size):
word = ''.join(perm)
if word in seen:
continue
seen.add(word)
if self.mediator.is_valid_word(word):
value = Computer._word_value(word)
if value > best_value:
best_value = value
best_word = word
return best_word
def play(self):
"""Επεκτείνει την Player.play(). Καλεί τον αντίστοιχο
αλγόριθμο ανάλογα με το level."""
print("*" * 60)
print(f" *** Παίκτης: {self.name} *** Σκορ: {self.score}")
print(f" >>> Γράμματα: {self.letters}")
print(f" (Επίπεδο αλγορίθμου: {self.level})")
print()
if self.level == 'MIN':
word = self._find_word_min()
elif self.level == 'MAX':
word = self._find_word_max()
else: # SMART
word = self._find_word_smart()
if word is None:
# Δεν βρήκε λέξη -> ζητά αλλαγή γραμμάτων
print(" -> Δεν βρήκα αποδεκτή λέξη, ζητώ αλλαγή γραμμάτων.")
return ('PASS', None)
return ('WORD', word)
def __repr__(self):
return f"Computer(name={self.name}, score={self.score}, level={self.level})"
# ---------------------------------------------------------------------------
# ΠΡΟΤΥΠΟ MEDIATOR
# ---------------------------------------------------------------------------
class Mediator:
"""Πρότυπο/μοτίβο Mediator: συντονίζει την επικοινωνία μεταξύ
των κλάσεων Game/Player/SakClass χωρίς αυτές να εξαρτώνται
άμεσα μεταξύ τους.
Ο Mediator κρατά αναφορές στο σακουλάκι και στο σύνολο των
αποδεκτών λέξεων. Οι παίκτες (Human/Computer) χρησιμοποιούν τον
Mediator για:
- να ελέγξουν αν μια λέξη είναι αποδεκτή (is_valid_word)
- να πάρουν νέα γράμματα (get_letters)
- να επιστρέψουν γράμματα στο σακουλάκι (put_back_letters)
"""
def __init__(self, sak, words_set):
self.sak = sak
self.words = words_set # set λέξεων -> O(1) lookup
self.game = None # ορίζεται από την Game στο setup
def register_game(self, game):
"""Η Game καλείται μετά τον Mediator. Έτσι ο Mediator
αποκτά αναφορά στο τρέχον παιχνίδι (αν χρειαστεί)."""
self.game = game
# ---------------- API προς τους παίκτες ----------------
def is_valid_word(self, word):
"""O(1) έλεγχος αν η λέξη ανήκει στο set των αποδεκτών."""
return word in self.words
def get_letters(self, n):
return self.sak.get_letters(n)
def put_back_letters(self, letters):
self.sak.put_back_letters(letters)
def add_word(self, word):
"""Χρησιμοποιείται από τον αλγόριθμο Smart-Learn (αν
επιλεγεί). Προσθέτει νέα λέξη στο set λέξεων."""
self.words.add(word)
def sak_size(self):
return len(self.sak)
# ---------------------------------------------------------------------------
# ΚΛΑΣΗ: Game
# ---------------------------------------------------------------------------
class Game:
"""Κεντρική κλάση που διαχειρίζεται το παιχνίδι συνολικά."""
def __init__(self, words_file='greek7.txt', human_name='Stavros',
computer_level='SMART'):
"""Αρχικοποίηση. Αποθηκεύει παραμέτρους και αρχικοποιεί τα
πεδία στο None· η πραγματική προετοιμασία γίνεται στο setup()."""
self.words_file = words_file
self.human_name = human_name
self.computer_level = computer_level
self.words = None # set με τις αποδεκτές λέξεις
self.sak = None # SakClass
self.mediator = None # Mediator
self.human = None # Human
self.computer = None # Computer
self.players = [] # λίστα παικτών (σειρά παιχνιδιού)
self.consecutive_passes = 0 # μετρητής για συνθήκη λήξης
# ----------------------- setup -----------------------
def setup(self):
"""Φορτώνει τις λέξεις, δημιουργεί σακουλάκι, παίκτες και
διαμοιράζει αρχικά γράμματα."""
# 1) Φόρτωση λέξεων -> set (Ο(1) αναζήτηση)
self.words = self._load_words(self.words_file)
# 2) Δημιουργία σακουλιού
self.sak = SakClass()
# 3) Δημιουργία mediator
self.mediator = Mediator(self.sak, self.words)
self.mediator.register_game(self)
# 4) Δημιουργία παικτών
self.human = Human(self.human_name, self.mediator)
self.computer = Computer('PC', self.mediator, level=self.computer_level)
self.players = [self.human, self.computer]
# 5) Αρχική διαμοίραση γραμμάτων (7 ανά παίκτη)
for p in self.players:
p.refill_letters()
self.consecutive_passes = 0
def _load_words(self, path):
"""Διαβάζει το greek7.txt και επιστρέφει set αποδεκτών λέξεων.
Δομή: set -> πολυπλοκότητα Ο(1) στον έλεγχο μέλους."""
words = set()
if not os.path.exists(path):
print(f" !! Προσοχή: δεν βρέθηκε το αρχείο {path}.")
print(" Το παιχνίδι θα συνεχίσει με κενό λεξιλόγιο.")
return words
with open(path, 'r', encoding='utf-8') as f:
for line in f:
w = line.strip().upper()
if w:
words.add(w)
return words
# ----------------------- run -----------------------
def run(self):
"""Κύριος βρόχος του παιχνιδιού."""
print("\n" + "=" * 60)
print(" ΞΕΚΙΝΑ ΤΟ ΠΑΙΧΝΙΔΙ!")
print("=" * 60)
game_over = False
while not game_over:
for player in self.players:
# Έλεγχος λήξης πριν παίξει ο επόμενος
if self._should_end():
game_over = True
break
# Συμπλήρωση γραμμάτων (αν χρειάζεται)
player.refill_letters()
# Αν ο παίκτης δεν έχει καθόλου γράμματα -> τέλος
if len(player.letters) == 0:
game_over = True
break
# Ο παίκτης παίζει
action, value = player.play()
if action == 'QUIT':
print(f"\n *** Ο {player.name} παραιτήθηκε. ***")
game_over = True
break
elif action == 'PASS':
# Αλλαγή όλων των γραμμάτων του παίκτη
print(f" -> {player.name}: αλλαγή γραμμάτων.")
self.mediator.put_back_letters(player.letters)
player.letters = []
player.refill_letters()
self.consecutive_passes += 1
elif action == 'WORD':
word = value
points = Computer._word_value(word)
player.add_score(points)
player.remove_letters(word)
print(f" Παίζω τη Λέξη: {word}")
print(f" Πόντοι Λέξης: {points}")
print(f" *** Παίκτης: {player.name} *** Σκορ: {player.score}")
player.refill_letters()
print(f" >>> Γράμματα: {player.letters}")
self.consecutive_passes = 0
print()
self.end()
def _should_end(self):
"""Επιστρέφει True αν το παιχνίδι πρέπει να τελειώσει:
- Έχει αδειάσει το σακουλάκι ΚΑΙ κανείς δεν μπορεί να παίξει,
ή έχουν γίνει πολλά συνεχόμενα pass."""
if self.sak.is_empty():
# Αν επίσης κανείς δεν έχει γράμματα -> τέλος
if all(len(p.letters) == 0 for p in self.players):
return True
# Αν και οι 2 παίκτες πέρασαν συνεχόμενα 2 φορές ο καθένας
if self.consecutive_passes >= 4:
return True
return False
# ----------------------- end -----------------------
def end(self):
"""Ανακοίνωση τέλους και νικητή."""
print("\n" + "=" * 60)
print(" ΤΕΛΟΣ ΠΑΙΧΝΙΔΙΟΥ")
print("=" * 60)
for p in self.players:
print(f" {p.name}: {p.score} πόντοι")
# Σύγκριση μέσω υπερφορτωμένων τελεστών
if self.human > self.computer:
print(f"\n >>> ΝΙΚΗΤΗΣ: {self.human.name} <<<")
elif self.computer > self.human:
print(f"\n >>> ΝΙΚΗΤΗΣ: {self.computer.name} <<<")
else:
print("\n >>> ΙΣΟΠΑΛΙΑ <<<")
print("=" * 60 + "\n")
def __repr__(self):
return (f"Game(human={self.human}, computer={self.computer}, "
f"sak_remaining={len(self.sak) if self.sak else 0})")