首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >“AI”号战舰的功能

“AI”号战舰的功能
EN

Code Review用户
提问于 2018-09-17 12:37:53
回答 2查看 808关注 0票数 0

我想要一些建议,我如何可以缩短我的功能从我的战舰游戏(以下)。本质上,它所做的是检查一个坐标是否是命中,如果它是,那么如果检查相邻的和弦,并射击一个将是命中(它作弊,基本上,这是好的)。

我想知道我能做些什么来使它更有效/更短/更整洁。

(如果你用我目前没有使用的技术来改进它,那么请解释一下上面的技巧。我想用一些我能理解的东西。)

代码语言:javascript
复制
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")
EN

回答 2

Code Review用户

发布于 2018-09-17 16:06:24

logic/presentation

您正在混合演示和业务逻辑。当信号表明AI赢了,就像董事会一样。如果你认为"_" S比"O"更好地表达了清澈的海洋,或者你想要另一种颜色而不是白色,那么你想看一看整个代码吗?

Tile

据我正确的记忆,战列舰板上的瓷砖有几个状态:(清澈的海洋,船只,射击,但没有击中,击中船只),其中清澈的海洋和船只对对手有相同的代表性。因此,与其使用字符串来表示此状态,不如使用enum

代码语言:javascript
复制
from enum import Enum

class Tile(Enum):
    CLEAR = "CLEAR"
    VESSEL = "VESSEL"
    SHOT = "SHOT"
    HIT = "HIT"

我想你的董事会目前只是一个列表的字符串。做一个稍微聪明一点的董事会是值得的。

代码语言:javascript
复制
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__,可以使访问板更加直观:

代码语言:javascript
复制
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

现在你可以做这样的事情

代码语言:javascript
复制
b = Board()
b[2, 3]

Tile.Clear

代码语言:javascript
复制
b[2, 3] = Tile.Vessel
b[2, 3]

Tile.Vessel

这样,您就不会在1索引环境中直接使用0索引列表来困扰用户。您甚至可以使用行名A到J,如果只需要对这2种方法稍加调整的话。

然后你可以添加这样的一艘船:

代码语言:javascript
复制
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函数来简化。

代码语言:javascript
复制
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

实际枪击事件本身也是如此:

代码语言:javascript
复制
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的船只,这取决于你从哪里开始。

AI类

这是第一次尝试。_last_hit可以使用一些改进,在这里使用collections.deque甚至set似乎比使用单个值更符合逻辑。但它显示了如何使用不同的方法来定义beahviour。

代码语言:javascript
复制
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()
票数 1
EN

Code Review用户

发布于 2018-09-19 09:58:39

就在这里,简化了Maarten Fabré's的建议

通过大量重复的代码,您可以简化此操作。

代码语言:javascript
复制
    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)

代码语言:javascript
复制
    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一样

代码语言:javascript
复制
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é提到的建议技巧

代码语言:javascript
复制
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

或者你可以用

代码语言:javascript
复制
0 <= y_coord + j <= 10 and 0 <= x_coord + i <= 10

我猜是10,因为你用了x = random.randint(1, 10)

递归不需要

我关心的另一件事是您使用的recursive,我认为这里不需要它,并且花费了大量的内存

我做了一个小测试,首先是没有递归的代码。

代码语言:javascript
复制
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来测试内存成本,用一个非常大的板

代码语言:javascript
复制
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()

这是非递归的结果。

代码语言:javascript
复制
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

这是递归的结果

代码语言:javascript
复制
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 B
票数 1
EN
页面原文内容由Code Review提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://codereview.stackexchange.com/questions/203870

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档