Juego de dados NEA Computing Task 2
Recientemente me asignaron la Tarea 2 como se ve a continuación y me doy cuenta de que alguien respondió la pregunta en este sitio aquí, pero quería una opinión nueva
TAREA 2:
- Permite que dos jugadores ingresen sus datos, que luego se autentican para asegurarse de que son jugadores autorizados.
- Permite a cada jugador lanzar dos dados de 6 caras.
- Calcula y genera los puntos de cada ronda y la puntuación total de cada jugador.
- Permite a los jugadores jugar 5 rondas.
- Si ambos jugadores tienen la misma puntuación después de 5 rondas, permite que cada jugador lance 1 dado cada uno hasta que alguien gane.
- Salidas quién ha ganado al final de las 5 rondas.
- Almacena la puntuación del ganador y su nombre en un archivo externo.
- Muestra el puntaje y el nombre del jugador de los 5 mejores puntajes ganadores del archivo 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()
Cualquier pensamiento / optimización será muy apreciado.
Respuestas
¡Bienvenido a Revisión de código!
PEP-8
En Python, es común (y recomendado) seguir la guía de estilo PEP-8 para escribir código limpio, mantenible y consistente.
Las funciones y variables deben nombrarse en a lower_snake_case, las clases como UpperCamelCasey las constantes como UPPER_SNAKE_CASE.
cuerdas f
Recientemente introducido en python 3 es el f-string; así que en lugar de tenerlo print("string", variable, "string")puedes hacer:
print(f"string {variable} string")
para el mismo efecto.
Funciones
Divida su código en funciones individuales más pequeñas, realizando tareas singulares. Algunos ejemplos serían, obtener el usuario / contraseña de users.txt, validar los detalles del usuario, leer las credenciales del usuario, etc.
if __name__ bloquear
Para los scripts, es una buena práctica poner su característica ejecutable dentro de la if __name__ == "__main__"cláusula.
Flujo de control
Intenta abrir el archivo de usuarios dos veces (lo mismo ocurre con el archivo de puntuaciones). A esto le sigue una definición de función, seguida de un código para que los usuarios inicien sesión. Luego tiene su primera declaración de importación seguida de otra definición de función y código posterior nuevamente.
Intente agrupar bloques. En Python (y casi todos los lenguajes de programación), las importaciones son lo primero (después de shebang). Luego, las constantes globales, las definiciones de funciones / clases y luego el código del script en sí.
with declaraciones
En lugar de administrar los descriptores de archivos abiertos y luego controlar el cierre de ellos, Python tiene una withdeclaración que envuelve esto para usted:
with open(your_file, mode) as f:
something = f.read()
# f.write(something)
esto cierra automáticamente (y mantiene dentro del alcance) el descriptor de archivo.
Atributos asociados
Tiene un elemento de jugador, que tiene propiedades asociadas como nombre de usuario, player_id, score. Esto se puede poner en una clase, en lugar de mantener 10 variables diferentes para cada jugador.
Métodos incorporados
En muchos lugares de su código, está haciendo un .replace("\n", ""). Esto no es realmente necesario, ya que los objetos de cadena en Python tienen un .strip()método que limpia todos los espacios en blanco (y nuevas líneas).
Del mismo modo, intenta capturar un FileNotFoundErrorsolo para generar otro error por la misma razón. Deje que el error definido específicamente para el trabajo lo haga. No es necesario capturar si desea que el programa falle en caso de que falte un archivo de todos modos.
Volver a escribir
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 reescritura permite múltiples jugadores, junto con la opción de establecer múltiples rondas ( NUM_PLAYERSy NUM_ROUNDS).
Hay muchas cosas en su código que se pueden refactorizar, pero comencemos desde el principio con algunas guías de estilo de Python (también llamadas PEP8 )
Importaciones
Se recomienda escribir todas las importaciones en la parte superior de su archivo.
Nombrar
En Python, el nombre de las funciones y variables debe ser snake_case d. Es decir, en lugar de def Roll()debería tener def roll(), en lugar de Player1Scoredebería tener player1_scorey así sucesivamente. Tienes la idea. Leer más sobre esto aquí
¡Di NO a `;` en Python!
No lo use ;en Python. No es necesario y me recuerda los tiempos difíciles en los que estaba usando C / C ++. No quieres que esté triste, ¿verdad? :(
General
Por lo general, es una buena práctica tratar de evitar bloques de código en línea. Es difícil de seguir y no tiene ningún beneficio. Esta:
if Player1Score > Player2Score: Winner = 1
Debe escribirse como:
if Player1Score > Player2Score:
Winner = 1
Dicho esto, su código hasta ahora, teniendo en cuenta los consejos anteriores, se vería así:
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()
Ahora, esto es un poco mejor. Analicemos ahora la implementación real.
Aquí:
try:
file = open("Users.txt", "r")
except FileNotFoundError:
raise SystemExit("User file not found")
file = open("Users.txt", "r")
Está intentando abrir un archivo y, si no existe, genera una excepción. No hay nada necesariamente malo en esto, pero observe cómo abre el archivo dos veces si no hay una excepción. Si desea verificar si un archivo existe o no, puede usar el osmódulo para confirmar si existe una ruta de archivo y crear una función que lo haga. Además, convierta la ruta de archivo en una constante y colóquela en la parte superior de su 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 llamar a la función puede hacer:
if check_path(USERS_FILEPATH):
# do things here
Hay un par de cosas nuevas aquí. Primero, observe cómo agregué una cadena de documentos a esta función y lo fácil que es saber qué hace realmente. En segundo lugar, observe lo fácil que es utilizar esta función en cualquier otro archivo que pueda utilizar su juego. En tercer lugar, observe cómo se formatea la cadena cuando presento una excepción. Puede leer más sobre el formato de cadenas de Python aquí .
// tengo que ir por ahora