NEA Computing Task 2 Dice Game

Oct 21 2020

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:

  1. Umożliwia dwóm graczom wprowadzenie swoich danych, które są następnie uwierzytelniane, aby upewnić się, że są uprawnionymi graczami.
  2. Pozwala każdemu graczowi rzucić dwoma sześciościennymi kośćmi.
  3. Oblicza i wyświetla punkty za każdą rundę i całkowity wynik każdego gracza.
  4. Pozwala graczom rozegrać 5 rund.
  5. Jeśli obaj gracze mają ten sam wynik po 5 rundach, każdy gracz może rzucić 1 kością, aż ktoś wygra.
  6. Wyniki, które wygrały na koniec 5 rund.
  7. Przechowuje wynik zwycięzcy i jego nazwisko w zewnętrznym pliku.
  8. 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

5 hjpotter92 Oct 21 2020 at 15:21

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).

4 GrajdeanuAlex Oct 21 2020 at 14:37

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ść