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)
#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_int
,int(input("some string"))
和input_int("some string")
,而是为这些值定义名称。 Python没有显式的常量声明,但是常量的标准是'C'
。 同样,Pep 8是官方的Python样式指南。如果您想编写别人可以理解的代码,强烈建议您阅读。
#3 楼
我认为您不需要getGrid
或setGrid
(至少不需要当前格式)。 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()
清除屏幕。参考文献:
堆栈溢出问题。
评论
\ $ \ 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