NEA Computing Task 2 Dice Game
Niedawno ustawiono mi zadanie 2, jak widać poniżej, i zdaję sobie sprawę, że ktoś odpowiada na pytanie na tej stronie , ale chciałem uzyskać świeżą opinię
ZADANIE 2:
- Umożliwia dwóm graczom wprowadzenie swoich danych, które są następnie uwierzytelniane, aby upewnić się, że są uprawnionymi graczami.
- Pozwala każdemu graczowi rzucić dwoma sześciościennymi kośćmi.
- Oblicza i wyświetla punkty za każdą rundę i całkowity wynik każdego gracza.
- Pozwala graczom rozegrać 5 rund.
- Jeśli obaj gracze mają ten sam wynik po 5 rundach, każdy gracz może rzucić 1 kością, aż ktoś wygra.
- Wyniki, które wygrały na koniec 5 rund.
- Przechowuje wynik zwycięzcy i jego nazwisko w zewnętrznym pliku.
- Wyświetla wynik i nazwę gracza dla 5 najlepszych zwycięskich wyników z zewnętrznego pliku.
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()
Wszelkie przemyślenia / optymalizacje będą bardzo mile widziane
Odpowiedzi
Witamy w przeglądzie kodu!
PEP-8
W Pythonie powszechne (i zalecane) jest przestrzeganie przewodnika stylistycznego PEP-8 dotyczącego pisania czystego, łatwego w utrzymaniu i spójnego kodu.
Funkcje i zmienne powinny być nazywane w a lower_snake_case, klasy jako UpperCamelCase, a stałe jako UPPER_SNAKE_CASE.
struny f
Nowością w Pythonie 3 jest f-string; więc zamiast mieć print("string", variable, "string")możesz:
print(f"string {variable} string")
dla tego samego efektu.
Funkcje
Podziel swój kod na mniejsze funkcje, wykonując pojedyncze zadania. Kilka przykładów to pobieranie użytkownika / hasła z users.txt, sprawdzanie danych użytkownika, odczytywanie danych uwierzytelniających użytkownika i tak dalej.
if __name__ blok
W przypadku skryptów dobrą praktyką jest umieszczenie funkcji wykonywalnej wewnątrz if __name__ == "__main__"klauzuli.
Kontrola przepływu
Próbujesz dwukrotnie otworzyć plik użytkowników (to samo dotyczy pliku wyników). Po tym następuje definicja funkcji, po której następuje kod, aby użytkownicy mogli się zalogować. Następnie masz pierwszą instrukcję importu, po której następuje kolejna definicja funkcji, a następnie ponownie kod.
Spróbuj zgrupować bloki razem. W Pythonie (i prawie wszystkich językach programowania) import jest pierwszą rzeczą (po shebang). Następnie stałe globalne, definicje funkcji / klas, a następnie sam kod skryptu.
with sprawozdania
Zamiast zarządzać otwartymi deskryptorami plików i później kontrolować ich zamykanie, Python ma withinstrukcję, która zawija to za Ciebie:
with open(your_file, mode) as f:
something = f.read()
# f.write(something)
to automatycznie zamyka (i utrzymuje w zakresie) deskryptor pliku.
Powiązane atrybuty
Masz element gracza, który ma powiązane właściwości, takie jak nazwa użytkownika, identyfikator gracza, wynik. Można to umieścić w klasie, zamiast utrzymywać 10 różnych zmiennych dla każdego gracza.
Wbudowane metody
W wielu miejscach w kodzie robisz .replace("\n", ""). Nie jest to naprawdę potrzebne, ponieważ obiekty łańcuchowe w Pythonie mają .strip()metodę, która czyści wszystkie białe spacje (i znaki nowej linii).
Podobnie próbujesz przechwycić a FileNotFoundErrortylko po to, aby wywołać inny błąd z tego samego powodu. Niech błąd zdefiniowany specjalnie dla zadania to zrobi. Nie ma potrzeby przechwytywania, jeśli i tak chcesz, aby program nie działał w przypadku braku pliku.
Przepisać
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()
UWAGA
Przepisanie pozwala na wielu graczy, wraz z opcją ustawienia wielu rund ( NUM_PLAYERSi NUM_ROUNDS).
W Twoim kodzie jest wiele elementów, które można refaktoryzować, ale zacznijmy od początku z niektórymi przewodnikami po stylach Pythona (zwanymi również PEP8 )
Import
Zaleca się zapisywanie wszystkich importów na początku pliku.
Nazewnictwo
W Pythonie nazwa funkcji i zmiennych powinna mieć postać snake_case d. Oznacza to, że zamiast tego def Roll()powinieneś def roll(), zamiast tego Player1Scorepowinieneś, player1_scorei tak dalej. Masz pomysł. Przeczytaj więcej na ten temat tutaj
Powiedz NIE dla `;` w Pythonie!
Nie używaj ;w Pythonie. Nie jest to potrzebne i przypomina mi ciężkie czasy, kiedy używałem C / C ++. Nie chcesz, żebym był smutny, prawda? :(
Generał
Zwykle dobrą praktyką jest unikanie wbudowanych bloków kodu. Trudno to śledzić i nie ma żadnych korzyści. To:
if Player1Score > Player2Score: Winner = 1
Powinien być zapisany jako:
if Player1Score > Player2Score:
Winner = 1
To powiedziawszy, Twój dotychczasowy kod, biorąc pod uwagę powyższe porady, wyglądałby tak:
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()
Teraz jest trochę lepiej. Omówmy teraz rzeczywiste wdrożenie.
Tutaj:
try:
file = open("Users.txt", "r")
except FileNotFoundError:
raise SystemExit("User file not found")
file = open("Users.txt", "r")
Próbujesz otworzyć plik, a jeśli nie istnieje, podnosisz i wyjątek. Nie ma w tym nic złego, ale zwróć uwagę, jak otwierasz plik dwa razy, jeśli nie ma zgłoszonego wyjątku. Jeśli chcesz sprawdzić, czy plik istnieje, czy nie, możesz użyć osmodułu, aby potwierdzić, czy ścieżka do pliku istnieje i utworzyć funkcję, która to robi. Uczyń również ścieżkę pliku stałą i umieść ją na początku skryptu:
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
Aby wywołać funkcję, możesz:
if check_path(USERS_FILEPATH):
# do things here
Jest tu kilka nowych rzeczy. Po pierwsze, zauważ, jak dodałem ciąg dokumentów do tej funkcji i jak łatwo jest powiedzieć, co tak naprawdę robi. Po drugie, zwróć uwagę, jak łatwo jest używać tej funkcji na innych plikach, z których może korzystać Twoja gra. Po trzecie, spójrz, jak jest formatowany ciąg, gdy zgłaszam wyjątek. Możesz przeczytać więcej o formatowaniu ciągów znaków w Pythonie tutaj .
// muszę już iść