这个问题来源于我提出的另一个问题。在前一个问题中,我试图使用蒙特卡罗算法;遗憾的是,它没有起作用。
因此,我决定尝试硬编码的人工智能,但不是21游戏,因为这将是太琐碎,对于一个更一般的游戏类似它。使用默认设置,您在下面看到的游戏是等于21游戏。
这个AI完全是由我建立的,从一开始就知道,在21场比赛中,要发挥出最好的效果,你必须始终把数字除以4. Ex (2 -> 4) (13 -> 16)。我试着想出一个更笼统的答案。
这种人工智能只在某些情况下神秘地发挥良好(例如:END_VALUE = 21 MINIMUM_PLAYABLE_NUMBER = 1 MAX_PLAYABLE_NUMBER = 4)。
如果你对人工智能和数学游戏知之甚少,请随意评论我的编程风格。
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()发布于 2014-11-03 23:47:33
既然您已经承认了AI算法中的缺陷,我将只回顾一下实现。
在AI()函数中有一个潜在的bug:
返回random.randint(1,MAX_PLAYABLE_NUMBER)
下界应该是MINIMUM_PLAYABLE_NUMBER。我对命名上的不一致感到困惑。常量应该是MIN_PLAYABLE_NUMBER和MAX_PLAYABLE_NUMBER,或者应该是MINIMUM_PLAYABLE_NUMBER和MAXIMUM_PLAYABLE_NUMBER。
按照惯例,每个逗号后面应该有一个空格。在PEP 8中没有明确说明这一点,但在所有示例中都暗示了这一点。
若要生成介绍性消息,请使用str.format()而不是字符串连接。
人类和人工智能之间有很多相似之处。因此,主循环可以从将它们转换为对象中获益。
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()发布于 2014-11-04 13:15:42
从顶部开始,常量的名称不一致:
MINIMUM_PLAYABLE_NUMBER
MAX_PLAYABLE_NUMBER您可以将第一个更改为MIN_PLAYABLE_NUMBER来解决这个问题。引入字符串是通过使用+操作符连接起来的,这对于多个片段并不是很有效。您可以使用string对象的格式化方法。
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的函数只是在移动。这样做的计算取决于其他功能。我建议您将所有这些都包装在类中,因为这样做有很好的理由:
data和behavior。目前的人工智能存在许多缺陷。你自己也注意到了。避免这样的事情:
def show_information_after_turn(value,who):
print("After " + who + "'s turn value is " + str(value))相反,只要当你想要它打印的时候就这么做。同样,如果您选择使用类内的“show”方法(如果您选择这样做的话),则会使这种情况变得更好。
还有一件事-- main函数目前正在执行player_is_first = False以在播放器之间循环。您可以通过使用以下命令来使其更加明显:
from itertools import cycle
for player in cycle((player1, player2)):
move(player)这样你就可以避免声明更多的变量,并且在你想要的时候很容易改变玩家的顺序。你所要做的就是将球员隔离到一个列表或一个元组中,然后改变它的顺序。不需要循环!
发布于 2014-11-03 22:38:32
第6行(INTRODUCTION=...):不要将值与字符串混合。相反,使用str.format:
mystring = """
The value of {name} is {value}
""".format(name='something', value='something else')除此之外,看上去还不错。我唯一的疑问可能是,你有很多功能,名字很长,身体也很短。虽然这当然是一种合法的风格,但我发现它掩盖了代码实际上所做的事情。例如,get_input_and_handle_errors实际上可以被命名为handle_input,dividers在max_divider_less_than_or_equal_max_playable_number中只被调用一次,所以它实际上不需要成为它自己的函数。
https://codereview.stackexchange.com/questions/68785
复制相似问题