Jan 30th, 2024
La laurea in ingegneria meccanica non mi ha fornito quasi nessuna conoscenza di base necessaria per la programmazione (tranne un po' di matematica e Matlab). Durante i miei anni di codifica professionale, ho acquisito enormi quantità di conoscenza legata all'informatica, ma occasionalmente sento ancora che c'è molto spazio per migliorare.
Ecco perché ho deciso di frequentare il corso CS50 di Harvard. Questo è il primo corso CS50 di Harvard che mi sono proposto di completare.
Ogni corso ha un progetto finale, la cui implementazione presenterò in questi post del blog legati a CS50.
Il gioco segue le regole standard del gioco dell'impiccato:
Il gioco ha due modalità:
Secondo le regole standard del gioco dell'impiccato, una parte dell'impiccato viene disegnata per ogni tentativo fallito. Se raggiungi dieci tentativi falliti, verrà disegnata un'immagine completa.
L'impiccato - Schermata iniziale
Dopo aver completato il gioco (vincendo o perdendo), puoi iniziare un altro gioco o uscire dal programma
L'impiccato - Partita persa
L'impiccato - Partita in corso
L'impiccato - Partita vinta
Per giocare al gioco, clona il progetto, installa Python, spostati nella cartella principale del repository, installa le dipendenze con
pip install -r requirements.txt
ed esegui
python project.py
nel terminale.
project.py
- come indicato nelle linee guida, il codice è composto da una funzione principale e tre funzioni allo stesso livello, necessarie per i test. Tuttavia, per motivi di gestione dello stato, ho inizializzato e mantenuto l'intero gioco all'interno di una classe Game, che si trova nello stesso file. Volevo evitare l'uso di variabili globali.
Le tre funzioni richieste allo stesso livello di indentazione della funzione principale implementano chiamate ai metodi della classe Game in modo che siano soddisfatti i requisiti finali del progetto. Ma devo sottolineare che tutti i metodi della classe sono stati testati accuratamente.
test_project.py
- contiene tutti i test del progetto
requirements.txt
- contiene tutte le dipendenze del pacchetto e le loro versioni utilizzate nel progetto
data/words.py
- contiene due liste di parole in inglese di cinque e sei lettere utilizzate nella selezione delle modalità di gioco
components/separators.py
- contiene funzioni utilizzate per disegnare il gioco dell'impiccato all'interno dell'interfaccia a riga di comando (CLI)
components/stages.py
- contiene le fasi di ciascuno dei disegni degli errori per il gioco dell'impiccato. Le fasi non seguono intenzionalmente il principio DRY, in modo che siano più facili da mantenere e revisionare. Se questa fosse un'applicazione più grande, dove le prestazioni sarebbero cruciali, queste fasi insieme ai disegni dello schermo iniziale potrebbero essere ottimizzate.
Come già menzionato in precedenza, la classe
Game
racchiude l'intera logica del gioco.
Ecco la scomposizione dei principali metodi e delle loro proprietà:
def __init__(self):
self.separator_length = 40
self.game_screen = 0
self.game_mode = "0"
self.guessed_letters = []
self.used_letters = []
self.word = ""
self.failures = 0
self.already_used_letter = ""
project.py - Game class
@property
def game_screen(self):
return self._game_screen
@game_screen.setter
def game_screen(self, n):
self._game_screen = n
@property
def game_mode(self):
return self._game_mode
@game_mode.setter
def game_mode(self, n):
self._game_mode = n
@property
def guessed_letters(self):
return self._guessed_letters
@guessed_letters.setter
def guessed_letters(self, new_values):
if len(new_values) == 2 and 0 <= new_values[1] < len(self.guessed_letters):
self._guessed_letters[new_values[1]] = new_values[0]
elif len(new_values) == 1:
self._guessed_letters = new_values[0]
else:
self._guessed_letters = []
@property
def used_letters(self):
return self._used_letters
@used_letters.setter
def used_letters(self, new_values):
if len(new_values) == 2 and new_values[1]:
self._used_letters.append(new_values[0])
elif len(new_values) == 1:
self._used_letters = new_values[0]
else:
self._used_letters = []
@property
def word(self):
return self._word
@word.setter
def word(self, n):
if n == "1":
self._word = random.choice(words_five_letters).upper()
elif n == "2":
self._word = random.choice(words_six_letters).upper()
else:
self._word = n # for testing purposes
@property
def failures(self):
return self._failures
@failures.setter
def failures(self, n):
self._failures = n
@property
def already_used_letter(self):
return self._already_used_letter
@already_used_letter.setter
def already_used_letter(self, n):
self._already_used_letter = n
project.py - Game class
def run_game(self):
self.clear_terminal()
while self.game_screen <= 1:
if self.game_screen == 0:
self.start_game()
while self.game_mode != "1" and self.game_mode != "2":
user_input = input("Mode: ")
self.game_mode = user_input
self.word = user_input
self.guessed_letters = [[" " for _ in range(len(self.word))]]
self.game_screen = 1
elif self.game_screen == 1:
self.clear_terminal()
self.main_game()
if self.is_game_finished():
user_input = input("Decision: ").upper()
else:
user_input = input("Guess a letter: ").upper()
self.already_used_letter = ""
if len(user_input) == 1 and user_input.isalpha():
###
## check if hit, miss, or repeat guess
# hit
if user_input in self.word and user_input not in self.used_letters:
indexes_of_hit = [index for index, char in enumerate(self.word) if char == user_input]
for index in indexes_of_hit:
self.guessed_letters = [user_input, index]
# repeat
elif user_input in self.used_letters:
self.already_used_letter = user_input
# miss
else:
self.failures = self.failures + 1
###
# add to used letters list
if user_input not in self.used_letters:
self.used_letters = [user_input, True]
elif user_input == "YES":
self.restart()
elif user_input == "NO":
self.quit()
break
project.py - Game class
def start_game(self):
draw_separator(self.separator_length)
draw_separator(self.separator_length)
self.empty_space()
self.empty_space()
self.empty_space()
center_text(text="HANGMAN", length=self.separator_length, draw_border=True, double_border=True)
self.empty_space()
self.empty_space()
self.empty_space()
draw_separator(self.separator_length)
draw_separator(self.separator_length)
self.empty_space()
center_text(text="Enter mode to start the game", length=self.separator_length, draw_border=True)
center_text(text="[1] - Normal mode", length=self.separator_length, draw_border=True)
center_text(text="[2] - Hard mode", length=self.separator_length, draw_border=True)
self.empty_space()
draw_separator(self.separator_length)
print("2023 - Alan Jereb".rjust(self.separator_length))
center_text(text="", length=self.separator_length)
project.py - Game class
def main_game(self):
draw_separator(self.separator_length)
self.empty_space()
stage_functions = [stage.zero, stage.one, stage.two, stage.three, stage.four, stage.five, stage.six,
stage.seven, stage.eight, stage.nine, stage.ten]
if 0 <= self.failures <= 10:
stage_functions[self.failures](self.separator_length)
self.empty_space()
self.empty_space()
if self.user_has_won():
center_text(text="You win!", length=self.separator_length, draw_border=True, double_border=True)
elif self.user_has_failed():
center_text(text="You lose!", length=self.separator_length, draw_border=True, double_border=True)
self.empty_space()
if not self.user_has_failed():
center_text(text=(" ".join(self.guessed_letters)), length=self.separator_length, draw_border=True, double_border=True)
center_text(text=("__ " * len(self.word)), length=self.separator_length, draw_border=True, double_border=True)
else:
center_text(text=self.word, length=self.separator_length, draw_border=True, double_border=True)
self.empty_space()
draw_separator(self.separator_length)
if self.is_game_finished():
center_text(text="To play another game type: yes", length=self.separator_length, draw_border=True)
center_text(text="To quit the game type: no", length=self.separator_length, draw_border=True)
else:
center_text(text="Used letters:", length=self.separator_length, draw_border=True)
center_text(text=", ".join(self.used_letters[0:9]), length=self.separator_length, draw_border=True)
center_text(text=", ".join(self.used_letters[9:]), length=self.separator_length, draw_border=True)
draw_separator(self.separator_length)
if len(self.already_used_letter):
print("You have already used letter", self.already_used_letter ,"!")
else:
center_text(text="", length=self.separator_length)
center_text(text="", length=self.separator_length)
project.py - Game class
def clear_terminal(self):
if platform == "Windows":
os.system("cls")
else:
os.system("clear")
project.py - Game class
def restart(self):
self.clear_terminal()
self.game_screen = 0
self.used_letters = []
self.failures = 0
self.game_mode = "0"
project.py - Game class
Completare il corso è stato un piccolo progetto divertente. Ho imparato abbastanza sulla sintassi di base di Python da poter costruire sopra di essa. Posso capire perché Python sia così popolare al giorno d'oggi, ma al tempo stesso ho alcune riserve. Trovo la sua sintassi un po' difficile da leggere (funzioni significativamente più lunghe) e preferirei vedere Python utilizzare più parentesi graffe e meno rientranze. La mancanza di sicurezza dei tipi in Python è anche qualcosa a cui non riesco ad abituarmi.