我想要一些建议,我如何可以缩短我的功能从我的战舰游戏(以下)。本质上,它所做的是检查一个坐标是否是命中,如果它是,那么如果检查相邻的和弦,并射击一个将是命中(它作弊,基本上,这是好的)。
我想知道我能做些什么来使它更有效/更短/更整洁。
(如果你用我目前没有使用的技术来改进它,那么请解释一下上面的技巧。我想用一些我能理解的东西。)
def ai_shoots(y_coord, x_coord, all_buttons, player_1_board, ai_score):
# print("yes")
if ai_score == 20:
popupwindow("The computer has won.")
if player_1_board[y_coord][x_coord] == ': ':
ai_score += 1
player_1_board[y_coord][x_coord] = 'X '
all_buttons[y_coord - 1][x_coord - 1].configure(text="X", fg="black", bg="red3")
if player_1_board[y_coord - 1][x_coord] == ': ':
ai_shoots(y_coord - 1, x_coord, all_buttons, player_1_board, ai_score)
elif player_1_board[y_coord + 1][x_coord] == ': ':
ai_shoots(y_coord + 1, x_coord, all_buttons, player_1_board, ai_score)
elif player_1_board[y_coord][x_coord - 1] == ': ':
ai_shoots(y_coord, x_coord - 1, all_buttons, player_1_board, ai_score)
elif player_1_board[y_coord][x_coord + 1] == ': ':
ai_shoots(y_coord, x_coord + 1, all_buttons, player_1_board, ai_score)
else:
x = random.randint(1, 10)
y = random.randint(1, 10)
ai_shoots(y, x, all_buttons, player_1_board, ai_score)
elif player_1_board[y_coord][x_coord] == 'X ' or player_1_board[y_coord][x_coord] == 'O ':
x = random.randint(1, 10)
y = random.randint(1, 10)
ai_shoots(y, x, all_buttons, player_1_board, ai_score)
else:
player_1_board[y_coord][x_coord] = 'O '
all_buttons[y_coord - 1][x_coord - 1].configure(text="O", fg="white")发布于 2018-09-17 16:06:24
您正在混合演示和业务逻辑。当信号表明AI赢了,就像董事会一样。如果你认为"_" S比"O"更好地表达了清澈的海洋,或者你想要另一种颜色而不是白色,那么你想看一看整个代码吗?
据我正确的记忆,战列舰板上的瓷砖有几个状态:(清澈的海洋,船只,射击,但没有击中,击中船只),其中清澈的海洋和船只对对手有相同的代表性。因此,与其使用字符串来表示此状态,不如使用enum
from enum import Enum
class Tile(Enum):
CLEAR = "CLEAR"
VESSEL = "VESSEL"
SHOT = "SHOT"
HIT = "HIT"我想你的董事会目前只是一个列表的字符串。做一个稍微聪明一点的董事会是值得的。
class Board:
def __init__(self, width=10, height=10):
self.width = width
self.height = height
self._board = [[Tile.CLEAR] * width for _ in range(height)]用于创建空板。
通过重载__getitem__和__setitem__,可以使访问板更加直观:
def _exists(self, x, y):
return 0 < x <= self.height and 0 < y <= self.width
def __getitem__(self, coord):
x, y = coord
if not self._exists(x, y):
raise IndexError
return self._board[x-1][y-1]
def __setitem__(self, coord, state):
x, y = coord
if not self._exists(x, y):
raise IndexError
self._board[x-1][y-1] = state现在你可以做这样的事情
b = Board()
b[2, 3]Tile.Clear
b[2, 3] = Tile.Vessel
b[2, 3]Tile.Vessel
这样,您就不会在1索引环境中直接使用0索引列表来困扰用户。您甚至可以使用行名A到J,如果只需要对这2种方法稍加调整的话。
然后你可以添加这样的一艘船:
def add_ship(self, length, x, y, orientation='horizontal'):
directions = {
'horizontal': (1, 0),
'vertical': (0, 1),
}
dx, dy = directions[orientation]
coordinates = [
(x + i * dx, y + i *dy)
for i in range(length)
]
try:
if any(self[x, y] != Tile.CLEAR for x, y in coordinates):
raise ValueError("No room for this ship")
except IndexError: # over the edge
raise ValueError("No room for this ship")
for x, y in coordinates:
self._board[x, y] = Tile.VESSEL在你的AI的实现中,在被击中后,你要让AI检查所有的邻居。这可以通过在neighbours上实现Board函数来简化。
def already_shot(self, x, y):
return self[x, y] in {Tile.SHOT, Tile.HIT}
def neighbours(self, x, y):
coordinates = ((x-1, y), (x+1, y), (x, y-1), (x, y+1))
for x, y in coordinates:
try:
yield (x, y), self.already_shot(x, y)
except IndexError:
pass实际枪击事件本身也是如此:
def shoot(self, x, y):
if self.already_shot(x, y):
raise ValueError("Already targeted this square")
if self[x, y] == Tile.VESSEL:
self[x, y] = Tile.HIT
return True
self[x, y] = Tile.SHOT
return False 您需要实现一种方法来检查是否所有船只都沉没了,以确定是否有人赢了。该方法属于Board,而不是人工智能方法。
使用这样的类来跟踪板的状态,可以让您测试程序中的部分内容。你可以写一个测试套件,这样就可以测试船只的位置,射击等等。
这个在哪里出现?据我所知,AI一直在射击,只要它能命中。我希望AI作为一个类来实现,它会记住它已经拍摄了什么,并且会被询问是否被游戏瞄准的坐标。
如果一艘船的最小长度是2,那么在随机寻找一艘船的时候,你只需要瞄准一半的区域,所以只有那些(x + y) % 2是1或0的船只,这取决于你从哪里开始。
这是第一次尝试。_last_hit可以使用一些改进,在这里使用collections.deque甚至set似乎比使用单个值更符合逻辑。但它显示了如何使用不同的方法来定义beahviour。
class AI:
def __init__(self, opponent_board):
self._board = opponent_board
self._last_hit = None
def random_shot(self):
while True:
x = random.randint(1, self._board.height)
y = random.randint(1, self._board.width)
if (x + y) % 2 == 0:
continue
try:
hit = self._board.shoot(x, y)
self.mark_hit(x, y, hit)
return hit
except ValueError:
continue
def mark_hit(self, x, y, hit):
if hit:
self._last_hit = x, y
else:
self._last_hit = None
def next_turn(self):
if self._last_hit is None:
return self.random_shot()
x, y = self._last_hit
if self._board[x, y] == Tile.HIT:
for x_, y_ in self._board.neighbours(x, y):
try:
hit = self._board.shoot(x, y)
self.mark_hit(x, y, hit)
return hit
except ValueError:
pass
self._last_hit = None
# No adjacent tiles to aim at if this got through here
return self.random_shot()发布于 2018-09-19 09:58:39
就在这里,简化了Maarten Fabré's的建议
通过大量重复的代码,您可以简化此操作。
if player_1_board[y_coord - 1][x_coord] == ': ':
ai_shoots(y_coord - 1, x_coord, all_buttons, player_1_board, ai_score)
elif player_1_board[y_coord + 1][x_coord] == ': ':
ai_shoots(y_coord + 1, x_coord, all_buttons, player_1_board, ai_score)
elif player_1_board[y_coord][x_coord - 1] == ': ':
ai_shoots(y_coord, x_coord - 1, all_buttons, player_1_board, ai_score)
elif player_1_board[y_coord][x_coord + 1] == ': ':
ai_shoots(y_coord, x_coord + 1, all_buttons, player_1_board, ai_score)
else:
x = random.randint(1, 10)
y = random.randint(1, 10)
ai_shoots(y, x, all_buttons, player_1_board, ai_score)至
directions = ((0,-1), (0,1), (-1,0), (1,0))
for i, j in directions:
if player_1_board[y_coord + j][x_coord + i] == ': ':
x, y = x_coord + i, y_coord + j
break
else:
x = random.randint(1, 10)
y = random.randint(1, 10)
ai_shoots(y, x, all_buttons, player_1_board, ai_score)这和马腾·法布雷-S一样
def neighbours(self, x, y):
coordinates = ((x-1, y), (x+1, y), (x, y-1), (x, y+1))
for x, y in coordinates:
try:
yield (x, y), self.already_shot(x, y)
except IndexError:
pass当然,您需要检查player_1_board的边界
像Maarten Fabré提到的建议技巧
def __getitem__(self, coord):
x, y = coord
if not self._exists(x, y):
raise IndexError
return self._board[x-1][y-1]
def __setitem__(self, coord, state):
x, y = coord
if not self._exists(x, y):
raise IndexError
self._board[x-1][y-1] = state或者你可以用
0 <= y_coord + j <= 10 and 0 <= x_coord + i <= 10我猜是10,因为你用了x = random.randint(1, 10)
recursive,我认为这里不需要它,并且花费了大量的内存我做了一个小测试,首先是没有递归的代码。
def ai_shoots(y_coord, x_coord, player_1_board, ai_score):
if player_1_board[y_coord][x_coord] == ': ':
player_1_board[y_coord][x_coord] = 'X '
directions = ((0,-1), (0,1), (-1,0), (1,0))
for i, j in directions:
if 0 <= y_coord + j <= 10 and 0 <= x_coord + i <= 10 and player_1_board[y_coord + j][x_coord + i] == ': ':
x, y = x_coord + i, y_coord + j
break
else:
x = random.randint(1, 10)
y = random.randint(1, 10)
return x, y, ai_score+1
elif player_1_board[y_coord][x_coord] == 'X ' or player_1_board[y_coord][x_coord] == 'O ':
x = random.randint(1, 10)
y = random.randint(1, 10)
return x, y, ai_score
else:
player_1_board[y_coord][x_coord] = 'O '
return x_coord, y_coord, ai_score
def start_game(board):
x, y, score = 0, 0, 0
while score < 20:
x, y, score = ai_shoots(y, x, board, score)
print("The computer has won.")我用tracemalloc来测试内存成本,用一个非常大的板
def malloc_test():
board = [[random.choice([": ","X ", "O "]) for _ in range(1000)] for _ in range(1000)]
tracemalloc.start(3)
a_b = [b[:] for b in board]
time1 = tracemalloc.take_snapshot()
start_game(a_b)
time2 = tracemalloc.take_snapshot()
stats = time2.compare_to(time1, 'lineno')
for stat in stats:
print(stat)
print()
tracemalloc.start(3)
a_b = [b[:] for b in board]
time1 = tracemalloc.take_snapshot()
ai_shoots(0,0,a_b,0)
time2 = tracemalloc.take_snapshot()
stats = time2.compare_to(time1, 'lineno')
for stat in stats:
print(stat)
print()这是非递归的结果。
test.py:55: size=496 B (+496 B), count=1 (+1), average=496 B
test.py:76: size=464 B (+464 B), count=1 (+1), average=464 B
test.py:45: size=456 B (+456 B), count=1 (+1), average=456 B这是递归的结果
test.py:23: size=10080 B (+10080 B), count=20 (+20), average=504 B
test.py:27: size=3528 B (+3528 B), count=7 (+7), average=504 B
test.py:86: size=504 B (+504 B), count=1 (+1), average=504 Bhttps://codereview.stackexchange.com/questions/203870
复制相似问题