NEA Computing Task 2 Dice Game
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:
- Permite que dois jogadores insiram seus dados, que são autenticados para garantir que são jogadores autorizados.
- Permite que cada jogador role dois dados de 6 lados.
- Calcula e produz os pontos de cada rodada e a pontuação total de cada jogador.
- Permite que os jogadores joguem 5 rodadas.
- 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.
- Saídas quem ganhou no final das 5 rodadas.
- Armazena a pontuação do vencedor e seu nome em um arquivo externo.
- 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
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).
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