NEA Computing Task 2 Dice Game

Oct 21 2020

Recentemente, defini a Tarefa 2, conforme mostrado abaixo, e percebi que alguém respondeu à pergunta neste site aqui, mas eu queria uma opinião nova

TAREFA 2:

  1. Permite que dois jogadores insiram seus dados, que são autenticados para garantir que são jogadores autorizados.
  2. Permite que cada jogador role dois dados de 6 lados.
  3. Calcula e produz os pontos de cada rodada e a pontuação total de cada jogador.
  4. Permite que os jogadores joguem 5 rodadas.
  5. Se ambos os jogadores tiverem a mesma pontuação após 5 rodadas, permite que cada jogador role 1 dado cada até que alguém ganhe.
  6. Saídas quem ganhou no final das 5 rodadas.
  7. Armazena a pontuação do vencedor e seu nome em um arquivo externo.
  8. Exibe a pontuação e o nome do jogador das 5 maiores pontuações vencedoras do arquivo externo.
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()

Quaisquer pensamentos / otimizações seriam muito apreciados

Respostas

5 hjpotter92 Oct 21 2020 at 15:21

Bem-vindo à revisão do código!

PEP-8

Em python, é comum (e recomendado) seguir o guia de estilo PEP-8 para escrever código limpo, sustentável e consistente.

As funções e variáveis ​​devem ser nomeadas em a lower_snake_case, classes como UpperCamelCasee constantes como UPPER_SNAKE_CASE.

cordas f

Recentemente introduzida no python 3 é a string f; então, em vez de ter print("string", variable, "string")você pode fazer:

print(f"string {variable} string")

para o mesmo efeito.

Funções

Divida seu código em funções individuais menores, realizando tarefas singulares. Alguns exemplos seriam, buscar usuário / senha users.txt, validar detalhes do usuário, ler credenciais do usuário e assim por diante.

if __name__ quadra

Para scripts, é uma boa prática colocar seu recurso executável dentro da if __name__ == "__main__"cláusula.

Controle de fluxo

Você tenta abrir o arquivo do usuário duas vezes (o mesmo é verdadeiro para o arquivo de pontuação). Isso é seguido por uma definição de função, seguida por um código para fazer com que os usuários façam o login. Então você tem sua primeira instrução de importação seguida por outra definição de função e código posterior novamente.

Tente agrupar os blocos. Em python (e em quase todas as linguagens de programação), as importações são a primeira coisa (depois de shebang). Em seguida, constantes globais, definições de funções / classes e o próprio código do script.

with afirmações

Em vez de você gerenciar descritores de arquivos abertos e, posteriormente, controlar o fechamento deles, o python possui uma withinstrução que envolve isso para você:

with open(your_file, mode) as f:
    something = f.read()
    # f.write(something)

isso fecha automaticamente (e mantém no escopo) o descritor de arquivo.

Atributos associados

Você tem o elemento player, que tem propriedades associadas como username, player_id, score. Isso pode ser colocado em uma classe, em vez de manter 10 variáveis ​​diferentes para cada jogador.

Métodos embutidos

Em vários lugares em seu código, você está fazendo um .replace("\n", ""). Isso não é realmente necessário, pois os objetos string em python têm um .strip()método que limpa todos os espaços em branco (e novas linhas).

Da mesma forma, você tenta capturar um FileNotFoundErrorapenas para levantar outro erro pelo mesmo motivo. Deixe o erro definido especificamente para o trabalho fazer isso. Não há necessidade de capturar se você quiser que o programa falhe em caso de arquivo faltando de qualquer maneira.


Reescrever

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

A reescrita permite vários jogadores, junto com a opção de definir várias rodadas ( NUM_PLAYERSe NUM_ROUNDS).

4 GrajdeanuAlex Oct 21 2020 at 14:37

Há muito em seu código que pode ser refatorado, mas vamos começar do início com alguns guias de estilo Python (também chamados de PEP8 )

Importações

É recomendável escrever todas as importações no início do arquivo.

Nomeação

Em Python, o nome das funções e variáveis ​​deve ser snake_case d. Ou seja, em vez de def Roll()você deveria ter def roll(), em vez de Player1Scoredeveria ter player1_scoree assim por diante. Você entendeu. Leia mais sobre isso aqui

Diga NÃO para `;` em Python!

Não use ;em Python. Não é necessário e me faz lembrar dos tempos difíceis em que estava usando C / C ++. Você não quer que eu fique triste, quer? :(

Geral

Geralmente, é uma boa prática tentar evitar blocos de código embutidos. É difícil de acompanhar e não traz nenhum benefício. Este:

if Player1Score > Player2Score: Winner = 1

Deve ser escrito como:

if Player1Score > Player2Score: 
    Winner = 1

Dito isso, seu código até agora, levando em consideração o conselho acima, seria assim:

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

Agora, isso é um pouco melhor. Vamos discutir a implementação real agora.

Aqui:

try:
    file = open("Users.txt", "r")
except FileNotFoundError:
    raise SystemExit("User file not found")
file = open("Users.txt", "r")

Você está tentando abrir um arquivo, e se ele não existir, você levanta uma exceção. Não há nada necessariamente errado com isso, mas observe como você abre o arquivo duas vezes se nenhuma exceção for levantada. Se você quiser verificar se um arquivo existe ou não, você pode usar o osmódulo para confirmar se existe um caminho de arquivo e criar uma função que faça isso. Além disso, torne o caminho do arquivo uma constante e coloque-o no topo do seu 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

Para chamar a função, você pode fazer:

if check_path(USERS_FILEPATH):
    # do things here

Existem algumas coisas novas aqui. Primeiro, observe como adicionei uma docstring a essa função e como é fácil dizer o que ela realmente faz. Em segundo lugar, observe como é fácil usar esta função em quaisquer outros arquivos que seu jogo possa utilizar. Terceiro, observe como a string é formatada quando eu levanto uma exceção. Você pode ler mais sobre a formatação de strings do Python aqui .

// tem que ir agora