我刚接触Python(这是我的第一语言),已经编码了几个星期。

我已经制作了几个简单的脚本来下载和操作一些财务数据,但是最近我想关于制作一个简单的子手游戏。我已经对其进行了彻底的测试,它似乎也可以正常工作。有关如何改进的一些反馈?

#import packages
from random_words import RandomWords
from colorama import Fore, Back, Style
rw = RandomWords()
import os
import time

#list of 7 possible states of the hangman
hangman_pics = ['''
  +---+
  |   |
      |
      |
      |
      |
=========''', '''
  +---+
  |   |
  O   |
      |
      |
      |
=========''', '''
  +---+
  |   |
  O   |
  |   |
      |
      |
=========''', '''
  +---+
  |   |
  O   |
 /|   |
      |
      |
=========''', '''
  +---+
  |   |
  O   |
 /|\  |
      |
      |
=========''', '''
  +---+
  |   |
  O   |
 /|\  |
 /    |
      |
=========''', '''
  +---+
  |   |
  O   |
 /|\  |
 / \  |
      |
=========''']

#define function to clear the screen
def clear_screen():
    return os.system('cls')

#game on/off switch
game_on = False

#opening statement
os.system('cls')
print ('Welcome to the Hangman!\n')

#ask player to start the game
while game_on is False: 
    game_start = input ('Would you like to start a new game? [y/n]... ').upper()
    if game_start == 'Y':
        game_on = True
    elif game_start == 'N':
        game_on = False
        clear_screen()
    else:
        clear_screen()
        print ("Please input [y] or [n]")
clear_screen()

while game_on is True:
    #generate a random word to guess and transform to a list
    word_to_guess = rw.random_word()
    word_to_guess_list = list(word_to_guess)
    length_of_word_to_guess = len((word_to_guess_list))
    #generate a placeholder list for tried but wrong guesses
    tried_but_wrong = []
    #create a representation of word_to_guess_list with hidden spaces
    hidden_word = ('_')*length_of_word_to_guess
    hidden_word_list = list(hidden_word)

    #info about generated word to guess
    print ('I have just generated a random word for you to guess!')
    print (f'\nThe word has {length_of_word_to_guess} letters.')

    #initialize the number of attempts left
    attempts_left = 6
    hangman_state = 0
    print (f'\nYou have {attempts_left} attempts left.')

    #start the while loop for the game's logic
    while attempts_left>0 or hidden_word_list != word_to_guess_list:

        #initialize the guessed letter
        guess = ('')

        #print the current state of the hangman
        print (hangman_pics[hangman_state])

        #print the letter already used an not in the word to guess
        if tried_but_wrong == []:
            pass
        else:
            print(f'\nTip: you have alread tried these letters: {tried_but_wrong}\n')

        #while loop for guessing a letter 
        while len(guess) != 1 or type (guess) != str:
            print (f'This is the word you are trying to guess:'+'\n'*2+f'{hidden_word_list}')
            guess = input ('\nPlease select a letter you think is in the hidden word... ')
        clear_screen()

        #check if guessed letter is in the word to guess and not already guessed
        if guess in word_to_guess_list and guess not in hidden_word_list:
            print (f'''Great! You guessed correctly, "{guess}" is in the word you are trying to guess.''')

            #check the indices of guessed letter(s)
            print (f'You have {attempts_left} attempts left.')
            indices_of_guessed_letter = [i for i, x in enumerate(word_to_guess_list) if x == guess]

            #replace blank spots in hidden_word_list with guessed letter(s)
            for indices in indices_of_guessed_letter:
                hidden_word_list [indices] = guess

        #inform the player that they already guessed the selected letter
        elif guess in hidden_word_list:
            print ('Woops! Looks like you have already guessed this one! Please try again!')

        #inform the player that they already tried that letter and it's not in the word to guess
        elif guess in tried_but_wrong:
            print (f'There is no "{guess}" in the word you are trying to guess, but you have already tried that one.')

        #else: inform the player that they guessed wrong
        else:
            print (f'There is no "{guess}" in the word you are trying to guess.')

            #add the guessed and wrong letter to a list of already tried guesses
            tried_but_wrong.append(guess)

            #reduce the number of attempts left
            attempts_left -= 1

            #progress the hangman state
            hangman_state += 1

            #print the info about the number of attempts left
            print (f'You have {attempts_left} attempts left.')

        #check for win or loss

        #check for win
        if word_to_guess_list == hidden_word_list:
            time.sleep(2)
            clear_screen()
            print (f'You correctly guessed the word, which is ' + Fore.GREEN + f'"{word_to_guess}"'+ Style.RESET_ALL+'.')
            print (f'You had {attempts_left} attempts left.')
            break

        #check for loss
        if attempts_left == 0:
            time.sleep(2)
            clear_screen()
            print
            print (f"You lost. The word you were trying to guess was " + Fore.RED+ f'"{word_to_guess}"' + Style.RESET_ALL + '.')
            print ('\nUnfortunately, you are dead.')
            print (hangman_pics[hangman_state])
            break

    #ask if player wants to replay
    restart = False
    while restart is False: 
        game_restart = input ('\nWould you like to start a new game? [y/n]... ').upper()
        if game_restart == 'Y':
            restart = True
            clear_screen()
            game_on = True
        elif game_restart == 'N':
            restart = True
            clear_screen()
            print ("Thank you for playing!")
            time.sleep(3)
            game_on = False
            clear_screen()
        else:
            clear_screen()
            print ("Please input [y] or [n]")


评论

顺便说一下好图片!

#1 楼

您定义了一个clear_screen函数,但是在顶部有
#opening statement
os.system('cls')  # Here
print ('Welcome to the Hangman!\n')

您也可以在那里使用该函数。

再远一点,您可以:
while game_on is True:

仅当is可能是除game_on之外的某些真值时,才需要进行True检查,并且您想检查它实际上是否仅等于True。虽然game_on只会每个都有TrueFalse值,所以您可以这样写:
while game_on:

不管怎么说,tried_but_wrong是一个列表,但您正在使用编写时可以进行成员资格测试
guess in tried_but_wrong

如果您像在这里一样使用in来测试成员资格,理想情况下,该集合不应该是列表。 x in some_list需要对整个列表进行检查,这可能是一项昂贵的操作。如果tried_but_wrong是一个集合,那会更好,因为您似乎根本不需要维护插入顺序。
tried_but_wrong = set()  # An empty set. Python doesn't have a literal for an empty set
. . .
if not tried_but_wrong:  # Empty sets and lists are falsey
. . .
tried_but_wrong.add(guess)

由于实现方式的原因,集合中的成员查找非常快。如果集合的目的只是为了跟踪您已经“看到”的内容,而您不关心顺序,则使用集合。

在Python 3中,print是一个函数调用,但您使用的是“分离的花括号”:
print ("Please input [y] or [n]")

所做的一切只是暂时使您的代码看起来像Python2。由于这是一个普通的函数调用,因此应将其格式化并使其“连接”调用:
print("Please input [y] or [n]")

代码也是如此:
hidden_word_list [indices] = guess

[indices]hidden_word_list的一部分。使索引浮动在那里使事情变得不太明显。保持它们的附着。
这不仅仅是我的话。 PEP 8(Python的样式指南)明确建议这样做。
关于空白样式,请确保二进制运算符周围有空白。
while attempts_left>0 or hidden_word_list != word_to_guess_list:

之类的行前后不一致并违反了指南。在>周围留出空间:
while attempts_left > 0 or hidden_word_list != word_to_guess_list:

即使您违反了PEP 8,您也不会与样式设定保持一致。您在某些地方分配了一些东西,但在其他地方却没有。始终如一。一致性和正确的命名是确保代码可读性的两个非常有价值的工具。

在某些地方,由于某些原因,您需要在字符串文字周围加上括号:
hidden_word = ('_')*length_of_word_to_guess
. . . 
guess = ('')

I我不确定为什么。暂时使它们看起来像元组。只需使用裸露的琴弦,然后在第一行中再次在*周围放置空格。

if tried_but_wrong == []:
    pass
else:
    print(f'\nTip: you have alread tried these letters: {tried_but_wrong}\n')

这有两件事了;我之前提到的其中之一:


空集合是虚假的。通常使用if some_coll来测试集合中是否包含元素(或者使用if not some_coll来测试其是否为空)是习惯用法。 。如有必要,只需取消条件即可。但是,这里甚至不需要求反:
 if tried_but_wrong:
    print(f'\nTip: you have already tried these letters: {tried_but_wrong}\n')





我还是喜欢一些东西:


您正在充分利用f弦。这肯定会使字符串构造更整洁。


您使用snake_case并使用描述性名称。两者都是很好的做法。



#2 楼

优化并不是一个很好的选择,但是如果您是编程新手,可以考虑以下方面的事情: br />
hangman_part = ['O','|','/','\','/','\']
hangman_base ='''
  +---+
  |   |
  0   |
 213  |
 4 5  |
      |
========='''


(我不使用Python,所以我可能错过了一些东西,反正这更适合这个想法)

这段代码牺牲了一些性能(通常可以忽略不计)以提高可维护性。假设您想更改绞刑架(或您的男人)的设计,现在只需要做一次!

当然,在您的特定情况下,没有太多可能的更改,也没有许多绞架要改变。但是您要在实践中做到这一点,并且在实践中,追求可维护性几乎总是一个好主意。

我想说这也提高了可读性,因为现在您的行数减少了,但由于我们增加了一些复杂性,因此不予批准。我猜可能会因读者而异。

评论


\ $ \ begingroup \ $
hangman_part = ['O','|','/','\','/','\']由于您添加了所谓的反斜杠,因此该行包含语法错误,并且看起来就像您是Python的新手,也许是一般的编程人员,我建议您在使用Python编写一些代码之前,先查看PEP8 python.org/dev/peps/pep-0008官方Python样式指南,以避免在范围内对我这样的错误(0,5)(这是6次btw,而不是5次尝试),表示为:对于range(5)中的i(无需添加0),请修改您的代码并运行它,而不是信念的飞跃。
\ $ \ endgroup \ $
–user203258
19年8月26日在19:19



\ $ \ begingroup \ $
@emadboctor :(我不使用Python,所以我可能已经错过了一些东西,无论如何,这是更多的主意)^^ ...如hangman_base中所见,只有[0-5]个数字(和零件)要更换,最多6个应该发出一些错误信息否?感谢您更正我的`\`
\ $ \ endgroup \ $
– Nomis
19年8月27日在8:42

\ $ \ begingroup \ $
@Nomis Python范围(开始,停止)对象是“ half-open”;它包括起始值,但不包括终止值。因此,range(0,5)仅包含5个值0、1、2、3、4。它类似于C / C ++循环:for(int i = 0; i <5; i ++){...}在这方面;循环在到达终点时而不是在到达终点后结束。
\ $ \ endgroup \ $
– AJNeufeld
19年8月27日在14:00

\ $ \ begingroup \ $
@AJNeufeld:哇,它是如此直观,以至于我从没在文档>>中找到这一部分。感谢你们两个指出并花时间解释它!
\ $ \ endgroup \ $
– Nomis
19年8月27日在14:16

#3 楼

欢迎进行代码审查...

导入语句


from random_words import RandomWords
from colorama import Fore, Back, Style



假设有人要运行您的程序,如果没有这个问题,他将如何运行?除非它是官方的Python模块,否则应将其包含在其余的代码中。

不使用第二个import语句,应省略/清除。

风格

我建议您查看Python官方风格指南PEP0008。

以下是一些评论:


#define function to clear the screen
def clear_screen():
    return os.system('cls')





文档字符串:
Python文档字符串(或docstring)提供了将文档与Python模块,函数,类和方法相关联的便捷方法。通过将字符串常量作为对象定义中的第一条语句来定义对象的文档字符串。您可以写一个文档字符串而不是函数上方的注释。

def clear_screen():
    """Clear the screen."""
    return os.system('cls')



注释:您可能希望在需要的基础上添加注释,并省略不必要的注释说明;根据PEP0008,您应谨慎使用注释。很多事情都是不言自明的。
注释以random_words开头,而不是以# comment开头。

# import packages
# define function to clear the screen
# opening statement


空白行:(pep008)可以使用多余的空白行#(分别)分隔相关功能组。一堆相关的单线之间(例如,一组虚拟实现)可以省略空白行。
行太长:PEP 8建议行应限制为79个字符。

while game_on is True:


可表示为:

while game_on:



运算符周围缺少空格:二进制运算符两侧都留有1个空格(+-/ * //&| ^%=> <==!=),但函数默认值除外。

hidden_word = ('_')*length_of_word_to_guess
print (f'You correctly guessed the word, which is ' + Fore.GREEN + 
f'"{word_to_guess}"'+ Style.RESET_ALL+'.')



错误

Tip: you have alread tried these letters: ['7']


输入无效:没有捕获诸如数字之类的有问题的输入(在上述情况下为7,程序表明我之前已经尝试过7)。
第一个问题中的无效输入也是如此(要开始输入游戏(y / n)-假设用户输入yes或no而不是n或y或N或Y,则程序应指示无效输入或具有涵盖此类可能情况的案例(不太可能有人输入yes或no )。

清除屏幕功能#comment仅在Windows系统上清除,对于Unix(包括Mac和Linux)系统,os.system('cls')否则将引发一些错误;您应该在文档字符串中指出该名称,或者实现另一个功能以支持Unix系统(我有一个Macbook,因此必须对其进行更改才能正常运行)。

您想开始一个新游戏吗?如果在游戏开始时答案是否定的,则为无限循环,因此用户被迫玩游戏;他没有实际选择!

程序假定用户仅输入小写字母。


os.system('cls')
print ('Welcome to the Hangman!\n')



为什么要使用os.system('clear'),而您已经定义了一个函数来执行此操作?为什么不...

clear_screen()
print ('Welcome to the Hangman!\n')


函数

函数是仅在调用时运行的代码块。您可以将数据(称为参数)传递给函数。函数可以返回数据。您可以将代码封装到执行不同任务的单独函数中。更好地提高了可读性/模块化性,并使其更易于调试/更改(如果要更改一些值,使其在代码中不断重复?)。
您需要下载此单词列表以运行代码(将文件与脚本放置在同一文件夹中)。

import random
import string

words = [word for word in open('random_words.txt').read().split()]
attempts = 6
hangman_pics = ['''
  +---+
  |   |
      |
      |
      |
      |
=========''', '''
  +---+
  |   |
  O   |
      |
      |
      |
=========''', '''
  +---+
  |   |
  O   |
  |   |
      |
      |
=========''', '''
  +---+
  |   |
  O   |
 /|   |
      |
      |
=========''', '''
  +---+
  |   |
  O   |
 /|\  |
      |
      |
=========''', '''
  +---+
  |   |
  O   |
 /|\  |
 /    |
      |
=========''', '''
  +---+
  |   |
  O   |
 /|\  |
 / \  |
      |
========='''][::-1]


def get_current_figure():
    """Print current state of Hangman."""
    print(hangman_pics[attempts])


def play_game():
    """Play game."""
    global attempts
    available_letters = list(string.ascii_lowercase)
    letters_guessed = []
    secret_word = random.choice(words)
    slots = ['_' for _ in range(len(secret_word))]
    valid_responses = ['y', 'yes', 'n', 'no']
    print('Welcome to the hangman!')
    confirm_start = input('Would you like to start a new game?' 'y/n: ').lower()
    while confirm_start not in valid_responses:
        print(f'Invalid response! {confirm_start}  Enter y/n')
        confirm_start = input('Would you like to start a new game?' 'y/n: ').lower()
    if confirm_start == 'n' or confirm_start == 'no':
        print('Thank you for playing Hangman.')
        print(29 * '=')
        exit(0)
    while confirm_start == 'y' or confirm_start == 'yes':
        if not attempts:
            get_current_figure()
            print(f"You're dead, the word was {secret_word}")
            break
        check_win = 0
        for letter in secret_word:
            if letter in letters_guessed:
                check_win += 1
        if check_win == len(secret_word):
            print(f'Well done! you win.\nThe correct word is {secret_word}')
        print(45 * '=')
        get_current_figure()
        print(f"Available letters: {''.join(available_letters)}")
        print(f"Letters used: {''.join(sorted(letters_guessed))}")
        print(f'You have {attempts} attempts left.')
        print(slots, '\n')
        letter_guessed = input('Guess a letter:  ').lower()
        if letter_guessed in letters_guessed:
            print(f'You already tried that letter {letter_guessed}')
            attempts -= 1
        if letter_guessed.isalpha() and len(letter_guessed) == 1 and letter_guessed not in letters_guessed:
            available_letters.remove(letter_guessed)
            letters_guessed.append(letter_guessed)
            if letter_guessed in secret_word and len(letter_guessed) == 1:
                for index, letter in enumerate(secret_word):
                    if letter_guessed == letter:
                        slots[index] = letter
                print(f'Correct guess! {letter_guessed} is in the word.')
            if letter_guessed not in secret_word:
                attempts -= 1
                print(f'Wrong guess! {letter_guessed} not in the word.')
        if not letter_guessed.isalpha() or len(letter_guessed) > 1:
            print(f'Invalid entry {letter_guessed}')
            print('You have been penalized and lost 2 attempts!')
            attempts -= 2


def replay_game():
    """Return True for a game replay, False otherwise."""
    valid_responses = ['y', 'yes', 'n', 'no']
    replay = input('Would you like to play another game? y/n ').lower()
    while replay not in valid_responses:
        print(f'Invalid response {replay}')
        replay = input('Would you like to play another game? y/n ').lower()
    if replay == 'y' or replay == 'yes':
        return True
    if replay == 'n' or replay == 'no':
        print('Thank you for playing Hangman.')
        print(29 * '=')
        exit(0)
        return False


if __name__ == '__main__':
    while True:
        play_game()
        replay = replay_game()
        if replay:
            attempts = 6
            play_game()
        else:
            exit(0)


评论


\ $ \ begingroup \ $
即使只是查看您的if __name__ ==“ __main__”块,也存在问题-在这里尝试进行全局变量的形式非常糟糕,只需将其作为参数传递给play_game()(然后将其作为参数传递给get_current_figure()的参数,或者只是将其设为play_game()中的局部变量。另外,get_current_figure()对于打印某些内容的东西来说是一个坏名字-它应该返回字符串(不打印它)或称为draw_current_figure()。
\ $ \ endgroup \ $
–杰克M
19年8月26日在12:33



\ $ \ begingroup \ $
我不喜欢全局变量,但是因为get_current_figure使用变量尝试打印状态。关于get_current_figure的命名,起初,我将其命名为print_current_figure,由于某种原因,编辑器突出显示了该名称,因此我将其更改为get_current_figure ...无论如何,print_current_figure更好
\ $ \ endgroup \ $
–user203258
19年8月26日在16:11

\ $ \ begingroup \ $
我建议您将重点更多放在此处正在审查的实际代码上,而不是注释上
\ $ \ endgroup \ $
–user203258
19年8月26日在16:30

\ $ \ begingroup \ $
可以说,在答案中加注代码比加注原始代码更重要,因为通过回答这个问题,您会自动假设自己具有某种权威地位-如果没有人说什么,那么OP(初学者)可以仅假定答案中的代码是好的代码,并且应该以这种方式进行编码。
\ $ \ endgroup \ $
–杰克M
19年8月26日在17:09

\ $ \ begingroup \ $
我想你有一点。
\ $ \ endgroup \ $
–user203258
19年8月26日在17:12

#4 楼

清除屏幕

os.system('cls')是清除屏幕的一种可怕方法。

跨平台的os.system('cls' if os.name == 'nt' else 'clear')替代品同样糟糕。正在做的是分支一个子进程,并在该子进程(当前是当前进程的代码和数据存储器的副本)中:


用“ shell替换当前进程映像“映像(例如cmd.exe/bin/sh
被运行以解释'cls''clear'命令,如果不是内置命令,则它们又可以派生另一个新的子进程来执行该命令。

此外,如果在外壳程序的cls上发现了另一个同名程序,则clear$PATH命令可能不是标准命令。屏幕:

import colorama

def clear_screen():
    print(colorama.ansi.clear_screen())

colorama.init()

clear_screen()


评论


\ $ \ begingroup \ $
谢谢小费!如果我不使用colorama怎么办?导入colorama只是为了清除屏幕仍然是一种最佳方法,还是我应该尝试其他方法?
\ $ \ endgroup \ $
–迈克
19年8月27日在14:07



\ $ \ begingroup \ $
colorama.ansi.clear_screen()是获取(IIRC)字符串“ \ x1b [2J””的详细方法。如果您正在运行的终端支持ANSI转义序列,则可以简单地使用print(“ \ x1b [2J”)。在Window上的非ANSI终端上,colorama.init()会将sys.stdout替换为其自己的流,该流将过滤掉ANSI转义序列并在其位置调用相应的Win32函数。您使用了Fore.GREEN和Fore.RED而不调用colorama.init()的事实表明该终端已经支持ANSI转义序列...
\ $ \ endgroup \ $
– AJNeufeld
19年8月27日14:41



#5 楼

布局

在其他答案中我没有注意到的一件琐碎的事情是

hangman_pics = ['''
 ...
=========''']


最好写成

hangman_pics = [
  '''
 ...
=========''',
]


仅仅因为用三引号引起来的多行字符串与此类列表中各个元素的常规缩进协议有些许混乱,但这并不意味着您不应该费心将打开和关闭方括号,使其尽可能突出(]为随后阅读该代码的人员重新建立缩进级别,此处为零缩进)。

我曾经编码过类似的东西,其中用三引号引起来的字符串必须跨越更多的行,而且不那么相似,然后我觉得动态创建列表可以使它更清晰。我认为这不是必需的,但仅供参考:

hangman_pics = []
hangman_pics.append( '''
    ...
''' )
hangman_pics.append( '''
    ...
''' )
# etc.