NEA Computing Task 2 Dice Game
Di recente ho impostato l'attività 2 come mostrato di seguito e mi rendo conto che qualcuno risponde alla domanda su questo sito qui, ma volevo una nuova opinione
COMPITO 2:
- Consente a due giocatori di inserire i propri dettagli, che vengono poi autenticati per assicurarsi che siano giocatori autorizzati.
- Consente a ogni giocatore di tirare due dadi a 6 facce.
- Calcola e restituisce i punti per ogni round e il punteggio totale di ogni giocatore.
- Consente ai giocatori di giocare 5 round.
- Se entrambi i giocatori hanno lo stesso punteggio dopo 5 round, consente a ciascun giocatore di tirare 1 dado ciascuno finché qualcuno non vince.
- Uscite chi ha vinto alla fine dei 5 turni.
- Memorizza il punteggio del vincitore e il suo nome in un file esterno.
- Visualizza il punteggio e il nome del giocatore dei primi 5 punteggi vincenti dal file esterno.
try: File = open("Users.txt","r")
except FileNotFoundError:
raise SystemExit("User file not found")
File = open("Users.txt", "r")
def Login(Username, Player):
File.seek(0)
for Line in File:
ValidUsername = Line.split(",")[0]
ValidPassword = Line.split(",")[1].replace("\n", "")
if Username == ValidUsername:
Password = input("Password: ")
if Password == ValidPassword:
print("Player",Player,"logged in")
print("")
return True
else: print("Invalid Details")
return False
try:
while True:
print("Player 1 Login")
Username1 = input("Username: ")
if Login(Username1, 1): break
print("")
while True:
print("Player 2 Login")
Username2 = input("Username: ")
if Username1 == Username2:
print("Double login detected")
elif Login(Username2, 2): break
print("")
except KeyboardInterrupt:
raise SystemExit("Exiting...")
finally:
File.close()
import random
Player1Score = 0
Player2Score = 0
def Roll():
Dice1 = random.randint(1, 6)
Dice2 = random.randint(1, 6)
print("You rolled a",Dice1,"and a",Dice2)
Change = Dice1 + Dice2
Change += 10 if (Dice1 + Dice2) % 2 == 0 else -5
if Change < 0: Change = 0
if Dice1 == Dice2:
Dice3 = random.randint(1, 6)
print("Your third roll is a",Dice3)
Change += Dice3
print("")
return Change
for X in range(5):
print("Play:",X + 1,"starting")
input("Player 1, press enter to roll: ")
Player1Score += Roll()
input("Player 2, press enter to roll: ")
Player2Score += Roll()
print("Player 1 now has a score of",Player1Score)
print("Player 2 now has a score of",Player2Score)
print("")
if Player1Score > Player2Score: Winner = 1
if Player1Score < Player2Score: Winner = 2
if Player1Score == Player2Score:
print("You both got the same score")
def SameScore():
input("Press enter to roll dice: ")
print("")
Dice1 = random.randint(1, 6)
Dice2 = random.randint(1, 6)
print("Player 1 rolled:",Dice1)
print("Player 2 rolled:",Dice2)
if Dice1 == Dice2: return False
if Dice1 > Dice2: return 1
if Dice1 < Dice2: return 2
Winner = False
while not Winner:
Winner = SameScore()
if Winner == 1:
Winner = Username1 + ": " + str(Player1Score)
print(Username1,"won with",Player1Score,"points")
print(Username2,"lost with",Player2Score,"points")
if Winner == 2:
Winner = Username2 + ": " + str(Player2Score)
print(Username2,"won with",Player2Score,"points")
print(Username1,"lost with",Player1Score,"points")
WinnerScore = int(Winner.split(": ")[1])
FileWritten = False
try:
File = open("Scores.txt", "r")
Data = File.readlines();File.close()
for X in range(len(Data)):
if WinnerScore > int(Data[X].split(": ")[1]):
Data.insert(X, Winner + "\n")
if len(Data) > 5: Data.pop(5)
FileWritten = True; break
if len(Data) < 5:
if not FileWritten: Data.append(Winner + "\n")
File = open("Scores.txt","w")
for X in Data:
File.write(X.replace("\n","") + "\n")
except FileNotFoundError:
File = open("Scores.txt","w")
File.write(Winner + "\n")
File.close()
print("")
File = open("Scores.txt","r")
print("Highscores:")
for Line in File:
if Line != "": print(Line.replace("\n", ""))
File.close()
Qualsiasi pensiero / ottimizzazione sarebbe molto apprezzato
Risposte
Benvenuto in Code Review!
PEP-8
In python, è comune (e consigliato) seguire la guida di stile PEP-8 per scrivere codice pulito, gestibile e coerente.
Le funzioni e le variabili dovrebbero essere denominate in a lower_snake_case, classi come UpperCamelCasee costanti come UPPER_SNAKE_CASE.
stringhe
Di recente introdotta in python 3 è la stringa f; quindi invece di averlo print("string", variable, "string")puoi fare:
print(f"string {variable} string")
per lo stesso effetto.
Funzioni
Suddividi il tuo codice in singole funzioni più piccole, eseguendo attività singolari. Alcuni esempi potrebbero essere il recupero di utente / password da users.txt, la convalida dei dettagli dell'utente, la lettura delle credenziali dell'utente e così via.
if __name__ bloccare
Per gli script, è una buona pratica inserire la funzionalità eseguibile all'interno della if __name__ == "__main__"clausola.
Flusso di controllo
Si tenta di aprire il file degli utenti due volte (lo stesso vale per il file dei punteggi). Questo è seguito da una definizione di funzione, seguita da codice per convincere gli utenti ad accedere. Quindi hai la tua prima istruzione di importazione seguita da un'altra definizione di funzione e di nuovo dal codice.
Prova a raggruppare i blocchi insieme. In python (e quasi tutti i linguaggi di programmazione), le importazioni sono la primissima cosa (dopo shebang). Quindi costanti globali, definizioni di funzioni / classi e quindi il codice dello script stesso.
with dichiarazioni
Invece di gestire i descrittori di file aperti e successivamente controllare la chiusura di quelli, python ha withun'istruzione che lo racchiude per te:
with open(your_file, mode) as f:
something = f.read()
# f.write(something)
questo chiude automaticamente (e mantiene nell'ambito) il descrittore di file.
Attributi associati
Hai l'elemento giocatore, che ha proprietà associate come nome utente, player_id, punteggio. Questo può essere inserito in una classe, invece di mantenere 10 variabili diverse per ogni giocatore.
Metodi integrati
In molti punti del codice, stai facendo un file .replace("\n", ""). Questo non è realmente necessario, poiché gli oggetti stringa in python hanno un .strip()metodo che pulisce tutti gli spazi (e le nuove righe).
Allo stesso modo, provi a catturare un FileNotFoundErrorsolo per sollevare un altro errore per lo stesso motivo. Lascia che l'errore definito specificamente per il lavoro lo faccia. Non è necessario acquisire se si desidera che il programma fallisca comunque in caso di file mancante.
Riscrivere
from operator import itemgetter
import random
USERS_FILE: str = "Users.txt"
SCORES_FILE: str = "Scores.txt"
NUM_PLAYERS: int = 2
NUM_ROUNDS: int = 5
def roll_die() -> int:
return random.randint(1, 6)
class Player:
def __init__(self, _id: int, name: str):
self._id = _id
self.name = name
self.score = 0
def add_score(self, value: int):
self.score += value
if self.score < 0:
self.score = 0
def turn(self):
input(f"{self.name}'s turn. Press enter to roll.")
dice_1 = roll_die()
dice_2 = roll_die()
print(f"{self.name} rolled {dice_1} and {dice_2}.")
change = dice_1 + dice_2
change += 10 if change % 2 == 0 else -5
if change < 0:
change = 0
if dice_1 == dice_2:
dice_3 = roll_die()
print(f"Third roll is {dice_3}")
change += dice_3
self.add_score(change)
def __str__(self) -> str:
return f"Player({self.name}): {self.score}"
def fetch_users() -> dict:
users = {}
with open(USERS_FILE, "r") as f:
users = dict([line.strip().split(",") for line in f])
return users
def authenticate(users: dict, name: str, password: str) -> bool:
return users.get(name) == password
def show_highscores() -> None:
with open(SCORES_FILE, "r") as f:
print(f.read())
def fetch_highscores() -> list:
scores = []
with open(SCORES_FILE, "r") as f:
for line in f:
name, score = line.strip().split(": ")
score = int(score)
scores.append((name, score))
return scores
def write_score(player: Player, limit: int = 5):
current_highscores = fetch_highscores()
current_highscores.append((player.name, player.score))
sorted_scores = sorted(current_highscores, key=itemgetter(1), reverse=True)
with open(SCORES_FILE, "w") as f:
for name, score in sorted_scores[:limit]:
f.write(f"{name}: {score}\n")
def get_player(_id: int, users: dict) -> Player:
print(f"Players {_id} login")
while True:
name = input("username: ")
password = input("password: ")
if authenticate(users, name, password):
return Player(_id, name)
print("Invalid details. Try again!")
def get_winning_player(players: list) -> Player:
def filter_winning_players(player_dices: list, value: int):
filtered = []
for player, dice in player_dices:
print(f"{player.name} rolled {dice}.")
if dice == value:
filtered.append(player)
return filtered
max_score = max([player.score for player in players])
winners = [player for player in players if player.score == max_score]
if len(winners) == 1:
return winners[0]
print(f"{len(winners)} players have the same score. Trying to determine single winning player.")
while True:
input("Press enter to roll dice: ")
dices = [roll_die() for _ in range(len(winners))]
max_dice = max(dices)
winners = filter_winning_players(zip(winners, dices), max_dice)
if len(winners) == 1:
return winners[0]
def game():
users = fetch_users()
players = [get_player(count + 1, users) for count in range(NUM_PLAYERS)]
for round in range(1, NUM_ROUNDS + 1):
for player in players:
player.turn()
print(f"Player scores at end of round {round}:")
for player in players:
print(str(player))
winner = get_winning_player(players)
print(f"Winner is {winner}.")
write_score(winner)
print("Highscores: ")
show_highscores()
if __name__ == "__main__":
game()
NOTA
La riscrittura consente a più giocatori, insieme all'opzione per impostare più round ( NUM_PLAYERSe NUM_ROUNDS).
C'è molto nel codice che può essere modificato, ma iniziamo dall'inizio con alcune guide di stile Python (chiamate anche PEP8 )
Importazioni
Si consiglia di scrivere tutte le importazioni all'inizio del file.
Denominazione
In Python, il nome delle funzioni e delle variabili dovrebbe essere snake_case d. Cioè, invece di def Roll()dovresti avere def roll(), invece di Player1Scoredovresti avere player1_scoree così via. Hai avuto l'idea. Per saperne di più qui
Dì NO a `;` in Python!
Non usare ;in Python. Non è necessario e mi fa ricordare i momenti difficili in cui stavo usando C / C ++. Non vuoi che sia triste, vero? :(
Generale
Di solito è buona norma cercare di evitare blocchi di codice in linea. È difficile da seguire e non ha alcun vantaggio. Questo:
if Player1Score > Player2Score: Winner = 1
Dovrebbe essere scritto come:
if Player1Score > Player2Score:
Winner = 1
Detto questo, il tuo codice fino ad ora, tenendo in considerazione il consiglio di cui sopra, sarebbe simile a questo:
import random
try:
file = open("Users.txt", "r")
except FileNotFoundError:
raise SystemExit("User file not found")
file = open("Users.txt", "r")
def same_score():
input("Press enter to roll dice: ")
print("")
dice_1 = random.randint(1, 6)
dice_2 = random.randint(1, 6)
print("player 1 rolled:", dice_1)
print("player 2 rolled:", dice_2)
if dice_1 == dice_2:
return False
if dice_1 > dice_2:
return 1
if dice_1 < dice_2:
return 2
def login(username, player):
file.seek(0)
for line in file:
valid_username = line.split(",")[0]
valid_password = line.split(",")[1].replace("\n", "")
if username == valid_username:
password = input("password: ")
if password == valid_password:
print("player", player, "logged in")
print("")
return True
else:
print("Invalid Details")
return False
def roll():
dice1 = random.randint(1, 6)
dice2 = random.randint(1, 6)
print("You rolled a", dice1, "and a", dice2)
change = dice1 + dice2
change += 10 if (dice1 + dice2) % 2 == 0 else -5
if change < 0:
change = 0
if dice1 == dice2:
dice3 = random.randint(1, 6)
print("Your third roll is a",dice3)
change += dice3
print("")
return change
try:
while True:
print("player 1 login")
username1 = input("username: ")
if login(username1, 1):
break
print("")
while True:
print("player 2 login")
username2 = input("username: ")
if username1 == username2:
print("Double login detected")
elif login(username2, 2):
break
print("")
except KeyboardInterrupt:
raise SystemExit("Exiting...")
finally:
file.close()
player1_score = 0
player2_score = 0
for x in range(5):
print("Play:", x + 1, "starting")
input("player 1, press enter to roll: ")
player1_score += roll()
input("player 2, press enter to roll: ")
player2_score += roll()
print("player 1 now has a score of", player1_score)
print("player 2 now has a score of", player2_score)
print("")
if player1_score > player2_score:
winner = 1
if player1_score < player2_score:
winner = 2
if player1_score == player2_score:
print("You both got the same score")
winner = False
while not winner:
winner = same_score()
if winner == 1:
winner = username1 + ": " + str(player1_score)
print(username1, "won with", player1_score, "points")
print(username2, "lost with", player2_score, "points")
if winner == 2:
winner = username2 + ": " + str(player2_score)
print(username2, "won with", player2_score, "points")
print(username1, "lost with", player1_score, "points")
winner_score = int(winner.split(": ")[1])
file_written = False
try:
file = open("Scores.txt", "r")
data = file.readlines()
file.close()
for x in range(len(data)):
if winner_score > int(data[x].split(": ")[1]):
data.insert(x, winner + "\n")
if len(data) > 5:
data.pop(5)
file_written = True
break
if len(data) < 5:
if not file_written:
data.append(winner + "\n")
file = open("Scores.txt", "w")
for x in data:
file.write(x.replace("\n", "") + "\n")
except FileNotFoundError:
file = open("Scores.txt", "w")
file.write(winner + "\n")
file.close()
print("")
file = open("Scores.txt", "r")
print("Highscores:")
for line in file:
if line != "":
print(line.replace("\n", ""))
file.close()
Ora va un po 'meglio. Parliamo ora dell'attuale implementazione.
Qui:
try:
file = open("Users.txt", "r")
except FileNotFoundError:
raise SystemExit("User file not found")
file = open("Users.txt", "r")
Stai cercando di aprire un file e, se non esiste, sollevi ed eccezione. Non c'è niente di necessariamente sbagliato in questo, ma nota come apri il file due volte se non viene sollevata alcuna eccezione. Se vuoi controllare se un file esiste o meno, puoi usare il osmodulo per confermare se esiste un filepath e creare una funzione che lo faccia. Inoltre, rendi costante il percorso del file e inseriscilo all'inizio dello script:
import os
USERS_FILEPATH = '/path/to/Users.txt'
def check_file(filepath):
"""Verify if a filepath exists.
Return True if a filepath exists. Otherwise raise an exception.
Arguments:
filepath (str): Path to a file
Returns:
True or raise an exception
"""
if not os.path.exists(filepath):
raise OSError('User filepath {} not found.'.format(filepath))
return True
Per chiamare la funzione puoi fare:
if check_path(USERS_FILEPATH):
# do things here
Ci sono un paio di cose nuove qui. Innanzitutto, nota come ho aggiunto una docstring a questa funzione e quanto sia facile dire cosa fa effettivamente. In secondo luogo, nota quanto sia facile utilizzare questa funzione su qualsiasi altro file che il tuo gioco potrebbe utilizzare. Terzo, guarda come viene formattata la stringa quando sollevo un'eccezione. Puoi leggere di più sulla formattazione delle stringhe di Python qui .
// per ora devo andare