首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >用硬编码AI计数游戏

用硬编码AI计数游戏
EN

Code Review用户
提问于 2014-11-03 21:01:03
回答 4查看 518关注 0票数 2

上一次失败的尝试

这个问题来源于我提出的另一个问题。在前一个问题中,我试图使用蒙特卡罗算法;遗憾的是,它没有起作用。

我正在做一个硬编码的人工智能

因此,我决定尝试硬编码的人工智能,但不是21游戏,因为这将是太琐碎,对于一个更一般的游戏类似它。使用默认设置,您在下面看到的游戏是等于21游戏。

启动假设

这个AI完全是由我建立的,从一开始就知道,在21场比赛中,要发挥出最好的效果,你必须始终把数字除以4. Ex (2 -> 4) (13 -> 16)。我试着想出一个更笼统的答案。

有时打得很好,有时不是

这种人工智能只在某些情况下神秘地发挥良好(例如:END_VALUE = 21 MINIMUM_PLAYABLE_NUMBER = 1 MAX_PLAYABLE_NUMBER = 4)。

编码风格部分

如果你对人工智能和数学游戏知之甚少,请随意评论我的编程风格。

代码语言:javascript
复制
import random

END_VALUE = 21
MINIMUM_PLAYABLE_NUMBER = 1
MAX_PLAYABLE_NUMBER = 3
INTRODUCTION =     """
    In this game you and the computer
    alternate counting up to """ + str(END_VALUE) + """ chosing a number
    from """+ str(MINIMUM_PLAYABLE_NUMBER) + """ to """ + str(MAX_PLAYABLE_NUMBER) + """.
    The first that arrives to """ + str(END_VALUE) + """ LOSES.
    """

def dividers(n):
    """
    Given a number n returns all its dividers from 2 to the number

    >>> dividers(46)
    [2, 23, 46]
    >>> dividers(100)
    [2, 4, 5, 10, 20, 25, 50, 100]
    """
    return [i for i in range(2,n+1) if n%i==0]

def max_divider_less_than_or_equal_max_playable_number(n):
    """
    Core component of the AI.
    Tries to get the max divider less than MAX_PLAYABLE_NUMBER.
    If fails plays random.

    >>> MAX_PLAYABLE_NUMBER = 6
    >>> max_divider_less_than_or_equal_max_playable_number(65)
    5

    >>> MAX_PLAYABLE_NUMBER = 8
    >>> max_divider_less_than_or_equal_max_playable_number(17)
    [random] # note that 1 is not included in the dividers

    """
    try:
        return max([i for i in dividers(n) if i <= MAX_PLAYABLE_NUMBER])
    except ValueError:  # There are no divisors
        return random.randint(MINIMUM_PLAYABLE_NUMBER,MAX_PLAYABLE_NUMBER)


def AI(current_value):
    """
    Plays a move trying to make the user have to play
    at END_VALUE - 1 so that the player loses and he wins.
    He does so by playing the so that
    (current_value + move) % max_divider_less_ \
    than_or_equal_max_playable_number(END_VALUE - 1) == 0:
    is True.
    This AI misteriously works only for some values of the
    constants at the start.
    """
    moves = range(MINIMUM_PLAYABLE_NUMBER,MAX_PLAYABLE_NUMBER + 1)
    for move in moves:
        if (current_value + move) % max_divider_less_than_or_equal_max_playable_number(END_VALUE - 1) == 0:
            return move
    return random.randint(1,MAX_PLAYABLE_NUMBER)

def get_input_and_handle_errors():
    """
    Asks the user for input.
    Checks if it is a number, if it isn't raises error with message.
    Checks if it is in the correct range, sif it isn't raises error with message.
    """
    try:
        temp = int(input("Enter a number "))
    except ValueError:
        raise TypeError("You must enter a number.")

    if MINIMUM_PLAYABLE_NUMBER <= temp <= MAX_PLAYABLE_NUMBER:
        player_move = temp
    else:
        raise ValueError("You must play a number from " + str(MINIMUM_PLAYABLE_NUMBER) + " to " + str(MAX_PLAYABLE_NUMBER) + " both limits included.")

    return temp

def show_information_after_turn(value,who):
    print("After " + who + "'s turn value is " + str(value))

def check_if_game_over(value):
    if value >= END_VALUE:
        return True

def ask_user_if_he_wants_to_be_first():
    temp = input("Do you want to be first? ")

    # I check "y" in temp.lower()
    # instead of temp.lower() == "yes"
    # To give more freedom to the user
    if "y" in temp.lower():
        return True
    elif "n" in temp.lower():
        return False

def main():
    """
    Main interface.
    """
    value = 0
    print(INTRODUCTION)
    player_is_first = ask_user_if_he_wants_to_be_first()
    while 1:
        if not player_is_first:
            #############################################
            # Computer's turn
            value += AI(value)
            show_information_after_turn(value,"computer")
            if check_if_game_over(value):
                print("Computer has lost")
                break

        player_is_first = False

        ##############################################
        # Player's turn
        player_move = get_input_and_handle_errors()
        value += player_move
        show_information_after_turn(value,"player")
        if check_if_game_over(value):
            print("You have lost")
            break


if __name__ == "__main__":
    main()
EN

回答 4

Code Review用户

回答已采纳

发布于 2014-11-03 23:47:33

既然您已经承认了AI算法中的缺陷,我将只回顾一下实现。

AI()函数中有一个潜在的bug:

返回random.randint(1,MAX_PLAYABLE_NUMBER)

下界应该是MINIMUM_PLAYABLE_NUMBER。我对命名上的不一致感到困惑。常量应该是MIN_PLAYABLE_NUMBERMAX_PLAYABLE_NUMBER,或者应该是MINIMUM_PLAYABLE_NUMBERMAXIMUM_PLAYABLE_NUMBER

按照惯例,每个逗号后面应该有一个空格。在PEP 8中没有明确说明这一点,但在所有示例中都暗示了这一点。

若要生成介绍性消息,请使用str.format()而不是字符串连接。

人类和人工智能之间有很多相似之处。因此,主循环可以从将它们转换为对象中获益。

代码语言:javascript
复制
import itertools
import random
import string

END_VALUE = 21
MINIMUM_PLAYABLE_NUMBER = 1
MAX_PLAYABLE_NUMBER = 3

class AI:
    @property
    def name_possessive_case(self):
        return "computer's"

    @property
    def losing_message(self):
        return 'Computer has lost.'


    @staticmethod
    def _dividers(n):
        """
        Given a number n returns all its dividers from 2 to the number

        >>> dividers(46)
        [2, 23, 46]
        >>> dividers(100)
        [2, 4, 5, 10, 20, 25, 50, 100]
        """
        return [i for i in range(2,n+1) if n%i==0]

    @staticmethod
    def _max_divider_less_than_or_equal_max_playable_number(n):
        """
        Core component of the AI.
        Tries to get the max divider less than MAX_PLAYABLE_NUMBER.
        If fails plays random.

        >>> MAX_PLAYABLE_NUMBER = 6
        >>> _max_divider_less_than_or_equal_max_playable_number(65)
        5

        >>> MAX_PLAYABLE_NUMBER = 8
        >>> _max_divider_less_than_or_equal_max_playable_number(17)
        [random] # note that 1 is not included in the dividers

        """
        try:
            return max([i for i in AI._dividers(n) if i <= MAX_PLAYABLE_NUMBER])
        except ValueError:  # There are no divisors
            return random.randint(MINIMUM_PLAYABLE_NUMBER, MAX_PLAYABLE_NUMBER)


    def play(self, current_value):
        """
        Plays a move trying to make the user have to play
        at END_VALUE - 1 so that the player loses and he wins.
        He does so by playing the so that
        (current_value + move) % max_divider_less_ \
        than_or_equal_max_playable_number(END_VALUE - 1) == 0:
        is True.
        This AI misteriously works only for some values of the
        constants at the start.
        """
        moves = range(MINIMUM_PLAYABLE_NUMBER, MAX_PLAYABLE_NUMBER + 1)
        for move in moves:
            if (current_value + move) % AI._max_divider_less_than_or_equal_max_playable_number(END_VALUE - 1) == 0:
                return move
        return random.randint(MINIMUM_PLAYABLE_NUMBER, MAX_PLAYABLE_NUMBER)


class Human:
    @property
    def name_possessive_case(self):
        return 'your'

    @property
    def losing_message(self):
        return 'You have lost.'

    def play(self, current_value):
        """
        Asks the user for input.
        Checks if it is a number, if it isn't raises error with message.
        Checks if it is in the correct range, sif it isn't raises error with message.
        """
        try:
            temp = int(input("Enter a number "))
        except ValueError:
            raise TypeError("You must enter a number.")

        if MINIMUM_PLAYABLE_NUMBER <= temp <= MAX_PLAYABLE_NUMBER:
            player_move = temp
        else:
            raise ValueError("You must play a number from " + str(MINIMUM_PLAYABLE_NUMBER) + " to " + str(MAX_PLAYABLE_NUMBER) + " both limits included.")

        return temp

def determine_player_order():
    yes_no = input("Do you want to be first? ")

    if "y" in yes_no.lower():
        return itertools.cycle((Human(), AI()))
    else:
        return itertools.cycle((AI(), Human()))

def main():
    print("""
    In this game you and the computer
    alternate counting up to {END_VALUE} chosing a number
    from {MINIMUM_PLAYABLE_NUMBER} to {MAX_PLAYABLE_NUMBER}.
    The first that arrives to {END_VALUE} LOSES.
    """.format(END_VALUE=END_VALUE, MINIMUM_PLAYABLE_NUMBER=MINIMUM_PLAYABLE_NUMBER, MAX_PLAYABLE_NUMBER=MAX_PLAYABLE_NUMBER))

    value = 0
    turns = determine_player_order()
    for player in turns:
        value += player.play(value)
        print("After %s turn value is %d" % (player.name_possessive_case, value))
        if value >= END_VALUE:
            print(player.losing_message)
            break


if __name__ == "__main__":
    main()
票数 2
EN

Code Review用户

发布于 2014-11-04 13:15:42

从顶部开始,常量的名称不一致:

代码语言:javascript
复制
MINIMUM_PLAYABLE_NUMBER
MAX_PLAYABLE_NUMBER

您可以将第一个更改为MIN_PLAYABLE_NUMBER来解决这个问题。引入字符串是通过使用+操作符连接起来的,这对于多个片段并不是很有效。您可以使用string对象的格式化方法。

代码语言:javascript
复制
INTRODUCTION = 'In this game you and the computer alternate counting up to '\
               '{0} chooing a number from {1} to {2}. The first that arrives '\
               'to {0} loses.'.format(END_VALUE, MINIMUM_PLAYABLE_NUMBER,
               MAX_PLAYABLE_NUMBER)

我不知道为什么要这样调用dividers函数。他们不是通常叫factors吗?max_divider_less_than_or_equal_max_playable_number这个名字不太像丙酮。您也使用其他功能完成了这一任务。而且,您已经突破了函数的每一步这一事实并不能使任何事情变得更容易。

名为AI的函数只是在移动。这样做的计算取决于其他功能。我建议您将所有这些都包装在类中,因为这样做有很好的理由:

  • 在这段代码中同时有databehavior
  • 像玩家这样的物体是非常明显的。继承模型很容易遵循。
  • 以后可以展开类(甚至子类)
  • 一个巨大的好处是,您可以将玩家的核心“移动”方法从正常的随机移动更改为智能移动,而无需更改代码的其余部分。

目前的人工智能存在许多缺陷。你自己也注意到了。避免这样的事情:

代码语言:javascript
复制
def show_information_after_turn(value,who):
    print("After " + who + "'s turn value is " + str(value))

相反,只要当你想要它打印的时候就这么做。同样,如果您选择使用类内的“show”方法(如果您选择这样做的话),则会使这种情况变得更好。

还有一件事-- main函数目前正在执行player_is_first = False以在播放器之间循环。您可以通过使用以下命令来使其更加明显:

代码语言:javascript
复制
from itertools import cycle
for player in cycle((player1, player2)):
    move(player)

这样你就可以避免声明更多的变量,并且在你想要的时候很容易改变玩家的顺序。你所要做的就是将球员隔离到一个列表或一个元组中,然后改变它的顺序。不需要循环!

票数 3
EN

Code Review用户

发布于 2014-11-03 22:38:32

编码风格

第6行(INTRODUCTION=...):不要将值与字符串混合。相反,使用str.format

代码语言:javascript
复制
mystring = """
The value of {name} is {value}
""".format(name='something', value='something else')

除此之外,看上去还不错。我唯一的疑问可能是,你有很多功能,名字很长,身体也很短。虽然这当然是一种合法的风格,但我发现它掩盖了代码实际上所做的事情。例如,get_input_and_handle_errors实际上可以被命名为handle_inputdividersmax_divider_less_than_or_equal_max_playable_number中只被调用一次,所以它实际上不需要成为它自己的函数。

票数 1
EN
页面原文内容由Code Review提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

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

复制
相关文章

相似问题

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