这个想法是,您输入船只数量,例如10。它将在10x10网格(0-9)上放置10艘船只。您可以输入一个坐标,它将告诉您是命中还是未命中。著名棋盘游戏的简化版本。


C表示“计算机”。这些未显示在网格中,而是存储在2D数组中。
H表示“命中”。不言自明。
M表示“小姐”。同样容易说明。
空/空格表示在该坐标处没有设置任何内容。

我创建此程序的目的是使其尽可能高效和易读。很难解释我的内心运作方式,因此我对此深表歉意。

最好在IDE之外(在控制台中)运行

代码:

import random, os

ships = int(input("Enter ship count: "))
gridSize = 10
game = [[' ' for x in range(gridSize)] for y in range(gridSize)]

def getGrid(x,y):
    return game[x][y]

def setGrid(x,y,char):
    game[x][y] = char

for i in range(ships):              
    x = random.randint(0,gridSize-1)
    y = random.randint(0,gridSize-1)
    setGrid(x,y,'C')

xLabel = " " * 3
for i in range(gridSize):           
    xLabel += str(i) + " "

result = "Make a move!"
hits = 0

while hits != ships:
    os.system("cls")                
    print(" " + result + " [Ships: " + str(ships) + ", Size: " + str(gridSize) + ", Hits: " + str(hits) + "]\n")
    print(xLabel)                   
    for x in range(gridSize):
        print(" " + str(x) + " ",end="")
        for y in range(gridSize):
            print(" " * 2 if getGrid(x,y) == 'C' else getGrid(x,y) + " ",end="")
        print("")

    xGuess = int(input("\n X: "))
    yGuess = int(input(" Y: "))

    if getGrid(xGuess,yGuess) == 'C':
        result = "Hit! (" + str(xGuess) + ":" + str(yGuess) + ")"
        setGrid(xGuess,yGuess,'H')
        hits += 1
    else:
        result = "Miss! (" + str(xGuess) + ":" + str(yGuess) + ")"
        setGrid(xGuess,yGuess,'M')

print("\nCongratulations, you won the game!")
os.system("pause >nul")


#1 楼

您的程序非常好,在顶部导入了一些功能和理解。
但是它们都可以改进。


要坚持使用PEP8,您每个程序只能导入一个模块import。
您需要更多功能,您正在使用的那些功能我不会使用,并且会激怒我。

这是一个不成文的概念,但是_被称为抛出变量。
由于丢弃了范围内的项目,您可以执行以下操作:

[' ' for _ in range(gridSize)]



我将更改显示行的方式,目前每次打印显示两个字符,
这很难读,而且效率不高。
我建议您使用列表理解功能。该行中的每个字符均为“ C”,否则为getGrid(x,y)
因此:

row = [" " if getGrid(x, y) == "C" else getGrid(x, y) for y in range(gridSize)]


要显示此行,应使用getGrid(x,y)。这将在板上的每个位置之间放置一个空格。

我要做的另一个主要更改是使用' '.join(row)
执行以下行:

print(" " + result + " [Ships: " + str(ships) + ", Size: " + str(gridSize) + ", Hits: " + str(hits) + "]\n")


所有的字符串连接和转换都很难理解,而str.format可以将其整理掉。
等效于:

print(" {} [Ships: {}, Size: {}, Hits: {}]\n".format(result, ships, gridSize, hits))


您现在可以说出字符串的格式,并且可以合理地猜测将要在那里使用的字符串。
几乎所有时间都可以。


其他更改我建议您将str.format更改为range,以获取enumerate的位置和显示网格的行。
使用行理解时,我将彻底删除x
要显示

for x, row in enumerate(game):
    print(' {} {}'.format(x, ' '.join([" " if y == "C" else y for y in row])))


我还将板子变量名称更改为range,以便人们在引用它时可以立即理解您的意思。

在Python中,您应使用board而不是snake_case进行功能变量和变量,因此pascalCase应该是gridSize

最后,在将用户输入转换为答案中的CAD97数字时,应使用try-except块。
这是因为如果我输入“ a”,程序将崩溃。

要改善grid_size的创建效果,您可以像对待行一样来考虑它,但是会显示从范围中获得的项目。这意味着您要执行的列表理解是:

row = [str(i) for i in range(gridSize)]
xLabel = " " * 3 + " ".join(row)


评论


\ $ \ begingroup \ $
我确实研究了Python函数的命名约定。虽然“ spaced_name”可能是标准,但我更喜欢“ mixedName”。谢谢您的答复,亲切的陌生人。
\ $ \ endgroup \ $
–user102973
16年4月15日在18:23

\ $ \ begingroup \ $
使用_一次性使用会破坏调试器中的代码。 _是最后一条语句的返回值。我会用别的东西,也许会忽略。
\ $ \ endgroup \ $
–马丁·乌丁
16年4月16日在21:00

\ $ \ begingroup \ $
@MartinUeding“有_是最后一条语句的返回值。”,在IDLE中,而不是在Python脚本中。 _也用于i18n转换查找。这三个文件记录在这里。另外我也不了解调试器的事情,你能用一个小的脚本说出哪个吗?
\ $ \ endgroup \ $
– Peilonrayz
16年4月16日在21:14

#2 楼

首先,让我说,我喜欢您完成界面的方式。对于命令行程序而言,这非常好而且很整洁。从用户的角度来看,这是一个不错的设计。

,但让我们继续进行潜在的改进。

首先,要介绍一些输入。

 Make a move! [Ships: 10, Size: 10, Hits: 0]

   0 1 2 3 4 5 6 7 8 9
 0
 1
 2
 3                 M
 4
 5
 6
 7
 8
 9

 X: 3
 Y: 8


您混合了X和Y坐标。我指定去(3,8)-超过3个单位,向下8个单位-而您已经选择(8,3)-超过8个单位,向下3个单位。这只是一个简单的问题,即意识到您的2D列表以行为主的顺序,并且您必须将其索引为game[y][x]而不是game[x][y]。它只是列表的列表,因此具有这样的结构:

 list 


因此要访问元素,您必须先指定行(y),然后再指定列(x)。正如SuperBiasedMan所说,您只需使用[0:[0:'a', 1:'b', 2:'c'], 1:[0:'d', 1:'e', 2:'f'], 2:[0:'g', 1:'h', 2:'i']] getGrid即可轻松做到这一点。

第二点是输入,这是我们不希望看到的:setGrid。由于您的程序当前处于布局状态,因此提供无效输入(例如空字符串或非数字)将使程序崩溃。如果有可能(并且确实是),您希望优雅地处理错误的输入。我将稍作介绍。

因此,继续讨论代码本身。毕竟,这就是代码审查。

在Python中,尽管您可以将代码放在最低的缩进级别以使其运行,但最好将其放入函数中。为此,请遵循以下一般概述:

import random, os

# your functions

def main():
    # your code

if __name__ == "__main__":
    main()


这将您的逻辑封装到ValueError: invalid literal for int()函数中,并在运行此脚本时调用它。请查看此StackOverflow问题以获取有关main的详细说明。基本上,它告诉Python仅在脚本作为主要任务运行时才运行此代码。

现在我们正在定义函数,让我们来处理输入问题。故障安全输入的基本思想是尝试解析输入,捕获所有错误(如果存在),然后在失败时重试。因此,if __name__ == "__main__"可能看起来像这样:有关如何设置字符串格式的建议,因此我将跳过这一部分。而不是直接在代码中引用input_intint(input("some string"))input_int("some string"),而是为这些值定义名称。 Python没有显式的常量声明,但是常量的标准是'C'

同样,Pep 8是官方的Python样式指南。如果您想编写别人可以理解的代码,强烈建议您阅读。

#3 楼

我认为您不需要getGridsetGrid(至少不需要当前格式)。 getGrid(x, y)实际上比game[x][y]更不清晰,更长。人们已经熟悉索引语法的含义,但是函数调用可能会做任何事情。

请注意,为什么不保留getGrid以便它可以执行某些操作。您永远不会检查播放器是否输入了有效的输入。您只需直接通过它们即可。您可以轻松地添加try except来捕获索引错误,以使getGrid成为有价值的功能:此功能。

评论


\ $ \ begingroup \ $
嘿,谢谢您的回复。不确定我为什么这么做,但是您的建议是完全正确的。我不是故意进行检查的,因为它不是“质量控制”的程序。
\ $ \ endgroup \ $
–user102973
16-4-15在17:12



#4 楼

我喜欢这样的小命令行游戏-让我想起C64的夏夜。令我感到烦恼的是,您正在随机选择一个新的网格位置,但您没有检查它是否已被占用。如果random.randint选择两个重复的x,y位置,那么您将永远无法达到最终游戏条件。

在我看来,这是进行简单递归的绝佳机会!递归很有趣,因为它乍一看有点弯腰,而且永远不会给女士们留下深刻的印象。 (完全公开:我的妻子对递归没有印象。)

我会把'random.randint'分配给一个单独的函数,然后执行以下操作:

def picker():
    x = random.randint(0,gridSize-1)
    y = random.randint(0,gridSize-1)
    if getGrid(x, y) == 'C':
        x, y = picker()
    return x, y


它检查是否正在使用该位置。如果是这样,它将再次(可能再次)调用自身,并返回最终结果。然后,将您的船只放置循环更改为:

for i in range(ships):              
    x, y = picker()
    setGrid(x,y,'C')


当然,如果您决定放置101艘以上的船只,它将一直运行直到它开始最大递归深度误差。 (不会花很长时间)

评论


\ $ \ begingroup \ $
非常感谢!我不久之前就意识到了这个错误,但是无法提出一个优雅的解决方案。
\ $ \ endgroup \ $
–user102973
16年4月15日在20:13

\ $ \ begingroup \ $
我认为这艘轮船显示出至少有可能。
\ $ \ endgroup \ $
– CAD97
16年4月15日在20:19

\ $ \ begingroup \ $
我不会说这是递归的绝佳机会。 while循环可以完成这项工作,与递归相比,它更快,更惯用,并且堆栈溢出更少。
\ $ \ endgroup \ $
– 200_success
16 Apr 15 '21:02

\ $ \ begingroup \ $
@ 200_success-是的!但是,如果选择的是转储程序并指向问题的异常,或者是无限制地无限运行的while循环,则获取该异常可能更好(出于调试目的)。当然,您可以编写一个while循环,其行为并非如此,但是.....哎呀,我没有更好的理由。我只是喜欢这种解决方案的递归。
\ $ \ endgroup \ $
–眨眼
16-4-15在21:20



#5 楼

您的os.system('cls')仅可在Windows上使用。

import os
def cls():
    os.system('cls' if os.name=='nt' else 'clear')
cls()


然后在定义函数cls之后,现在只需要单线cls()清除屏幕。

参考文献:
堆栈溢出问题。