首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >NIM (基本AI策略)游戏

NIM (基本AI策略)游戏
EN

Code Review用户
提问于 2017-04-11 09:14:52
回答 2查看 9.6K关注 0票数 9

我试着在python中创建NIM游戏。我研究了几个关于如何总是在NIM比赛中获胜的视频。最后,我找到了一个方法,并且能够在我的代码中实现它。这就是结果..。

代码语言:javascript
复制
#---------------------
#      NIM GAME
#  Author : Akil Riaz
#---------------------

import random
import time

def welcome_message():
    print("""
----------------------------------------------------------------------------
                        WELCOME TO THE GAME OF NIM!
----------------------------------------------------------------------------
Rules: 1 - The user plays against the computer.
       2 - Only 1-3 sticks can be taken at a time.
       3 - The person who is left with the last stick is the loser.
       4 - Have FUN!
----------------------------------------------------------------------------
""")

def display_board(board):
    print("\n----------------------------------------------------------------------------")
    print("      ",*board, sep="   ")
    print("----------------------------------------------------------------------------\n")

def who_starts(question):
    choice = None
    while choice not in ("F", "S","f","s"):
        choice = input(question)
        if choice.upper() == "F":
            f_player = "USER"
            s_player = "USER"
        elif choice.upper() == "S":
            f_player = "CMP"
            s_player  = "CMP"
        else:
            print("\nInvalid choice. Please re-enter your choice.")

    return f_player, s_player

def users_pick(question,minStick,maxStick,userIn,board):
    print("\n--USER'S TURN--")
    while userIn not in range(minStick, maxStick) or userIn >= len(board):
        try:
            userIn = int(input(question))
            if userIn not in range(minStick, maxStick) or userIn >= len(board):
                print("\nYou can cannot choose that many sticks!")
                userIn = int(input(question))

        except Exception as e:
            print("\nAn error has occured.\nError: " + str(e) + "\nPlease re-enter your choice.")

    f_player = "CMP"

    return userIn, f_player

def update_board(board,move):
    valid_moves = (1,2)
    if len(board) > move:
        for i in  range(move):
            board.remove("/")
        return board
    elif len(board) <= move:
        if move in valid_moves:
            for i in range(move):
                board.remove("/")
            return board
        else:
            print("\n" + str(move) + " sticks cannot be taken.\nThe move you made was inavalid. Please  re-enter.")
            return board

def computers_move(board,userIn,s_player,winning_position,earlier_move):
    best_move = 1
    print("\n--COMPUTER'S TURN--")

    if s_player == "CMP" and winning_position == False:
        if len(board) % 4 ==1:
            best_move = 0
            while best_move not in range(1,len(board)+1):
                best_move = random.randint(1,3)
        else:
            if userIn + earlier_move < 4:
                best_move = 4 - (userIn + earlier_move)
                winning_position = True
            elif userIn + earlier_move > 4:
                best_move = 8 - (userIn + earlier_move)
                winning_position = True

    elif s_player == "USER" or winning_position == True:
        best_move = 4 - userIn

    earlier_move = best_move

    print("\nThe computer chooses to remove...")
    time.sleep(1)
    print(": " + str(best_move) + " stick(s).")
    for sticks in range(best_move):
        board.remove("/")

    f_player = "USER"
    return board, f_player, winning_position, earlier_move

def game_over(board,f_player):
    if len(board) == 1 and f_player == "USER":
        winner = "COMPUTER"
        loser = "USER"
    elif len(board) == 1 and f_player == "CMP":
        winner = "USER"
        loser = "COMPUTER"
    else:
        winner = None

    return winner, loser

def keepPlaying():
    while True:
        another_go = input("\nDo you want to play again?[Y/N]: ")
        if another_go in ("y","Y"):
            return True
        elif another_go in ("n","N"):
            return False
        else:
            print("\nInavlid choice. Please re-enter.")

def main():
    anotherGo = True
    welcome_message()
    while anotherGo == True:
        earlier_move = 0
        winning_position = False
        nimBoard = ["/","/","/","/","/","/","/","/","/","/","/","/","/","/","/","/","/"]
        winner = None
        userIn = 0
        print("----------------------------------------------------------------------------")
        display_board(nimBoard)
        print("\nWe begin with " + str(len(nimBoard)) + " sticks.")
        f_player, s_player = who_starts("\nDo you want to start first or second? [F for fisrt/S for second]: ")
        while len(nimBoard) != 1:
            if f_player == "USER":
                time.sleep(0.5)
                userIn,f_player = users_pick("\nEnter your choice [1, 2 or 3]: ", 1, 4, 20,nimBoard)
                nimBoard = update_board(nimBoard,userIn)
                display_board(nimBoard)
                print("\nThere are " + str(len(nimBoard)) + " stick(s) remaining.")
            else:
                time.sleep(1)
                nimBoard, f_player,winning_position, earlier_move = computers_move(nimBoard,userIn,s_player,winning_position,earlier_move)
                time.sleep(1)
                display_board(nimBoard)
                print("\nThere are " + str(len(nimBoard)) + " stick(s) remaining.")
        time.sleep(1)
        print("\nFinally...\nOnly one stick remains...")
        game_winner, game_loser = game_over(nimBoard,f_player)
        time.sleep(1)
        print("\nThe " + game_loser + " is left with the last stick and so he is the loser...")
        print("\nThe winner of the game is...\n: " + str(game_winner))
        anotherGo = keepPlaying()
        time.sleep(1)
    print("\nThank you for playing!")
    print("\n--GAME OVER--")

if __name__ ==  "__main__":
    main()

我没有遵循传统的方法,有不同的行/成堆的棍子,因为我发现这太具有挑战性(尽管我将在以后尝试)。这台电脑大部分时间都赢了。但是,如果用户知道我使用的策略,那么它是可以战胜的。

但我仍然觉得还有改进的余地。有什么可以改进的吗?

如果你们能提供任何简单的效率改进或任何形式的逻辑改进,我将不胜感激。

刚刚对程序做了一个简单的改进,计算机使用它的早期移动来确定一个更好的移动来击败用户.谢谢你的投票!)

EN

回答 2

Code Review用户

发布于 2019-08-06 00:22:16

通用编码风格和其他有用的技巧

提示1使用条件时,不需要键入x == True,这意味着:

代码语言:javascript
复制
while anotherGo == True:

可以是:

代码语言:javascript
复制
while anotherGo:

提示2.不要毫无意义地使用time.sleep。它可能会造成混淆,为什么你要使用它,我认为,你使用它只是为了使程序不太快,但.你为什么要那样做?)

提示3使用if/else时,可以将通用代码移到条件的末尾或开头,这样可以删除代码重复:

代码语言:javascript
复制
if f_player == "USER":
    time.sleep(0.5)
    userIn,f_player = users_pick("\nEnter your choice [1, 2 or 3]: ", 1, 4, 20,nimBoard)
    nimBoard = update_board(nimBoard,userIn)
    display_board(nimBoard)
    print("\nThere are " + str(len(nimBoard)) + " stick(s) remaining.")
else:
    time.sleep(1)
    nimBoard, f_player,winning_position, earlier_move = computers_move(nimBoard,userIn,s_player,winning_position,earlier_move)
    time.sleep(1)
    display_board(nimBoard)
    print("\nThere are " + str(len(nimBoard)) + " stick(s) remaining.")

变成:

代码语言:javascript
复制
if f_player == "USER":
    userIn,f_player = users_pick("\nEnter your choice [1, 2 or 3]: ", 1, 4, 20,nimBoard)
    nimBoard = update_board(nimBoard,userIn)

else:
    nimBoard, f_player,winning_position, earlier_move = computers_move(nimBoard,userIn,s_player,winning_position,earlier_move)

display_board(nimBoard)
print("\nThere are " + str(len(nimBoard)) + " stick(s) remaining.")

提示4如果您的所有打印都以换行开始,您可以考虑创建一个助手函数,这样您就不必每次都重新键入它:

代码语言:javascript
复制
def printn(s):
    print("\n" + s)

提示5为这些“魔术数字”创建常量。当我读到:

users_pick("\nEnter your choice [1, 2 or 3]: ", 1, 4, 20,nimBoard)

提示6您不支付每写字符,使用有意义的变量名称

您可以考虑将其命名为userIn,而不是有一个名为userInput的变量。这是..。更多的字符,但它所做的也更清楚

我需要检查函数定义,看看1,4,20是什么意思。如果您有名为MIN_STICK = 1, MAX_STICKS=4, USERIN=20的常量。

Python技巧

提示1如果多次创建具有相同值的数组,则可以:

代码语言:javascript
复制
# Notice how nice it is not to have to count to see the number of sticks
["/"] * 17

而不是:

代码语言:javascript
复制
["/","/","/","/","/","/","/","/","/","/","/","/","/","/","/","/","/"]

但我也认为你根本不需要这个数组。您可以简单地将棒数保持为int,并在必要时减少它。这个数组用于打印电路板,所以不应该将显示逻辑与游戏逻辑混合,这样可以使代码更易于维护和读取。

提示2只使用一种命名样式

您可以将snake_casecamelCase混合到您的代码中,这使得代码更难阅读。由于您使用的是Python,所以您应该坚持使用snake_case

代码“清洁”

users_pick方法中,您传递一个高于最大接受值的userIn,这样您的while循环才能工作(我怀疑)。不要这样做,事实上,您根本不应该将userIn作为参数传递,因为您完全忽略了输入。

再次出现在users_pick。这样做:userIn not in range(minStick, maxStick)可能看起来“很棒”,但它也意味着每次您调用它时都会生成一个范围,只为了检查值是否在其中,为什么不使用速度更快、更多的userIn < minStick or userIn > maxStick .合乎逻辑。

我不确定这是否是复制/粘贴错误,但在who_starts方法中,您将f_players_player设置为相同的值。

总之,我认为您应该尝试将代码中的显示逻辑和游戏逻辑分开。您在用户输入方面做得很好,但例如,您不需要保留一个/数组来查看游戏的位置。

票数 2
EN

Code Review用户

发布于 2021-05-03 08:27:12

这已经是很久以前的事了,但是已经对我自己的版本进行了编码,对您的代码进行了一些注释。

一般来说:

  • 你可以做得更简单。特别是,不需要winning_positionearlier_move变量,只需知道len(board) % 4的值就可以得到所需的一切。
  • 无论是计算机还是用户首先播放,也不应该改变算法的第一步。

详情如下:

  • 不要使用拒绝: best_move =0的for循环,而best_move不在范围内(1,len(板)+1):best_move = random.randint(1,3)

当您只需使用min时:

代码语言:javascript
复制
        best_move = random.randint(1,min(3,len(board))
  • @ieatbagels的所有评论都是相关的。

我提供了一个基于罗塞塔码版本的更简单的版本(但在Rosetta的初始条件下,计算机总是获胜)

代码语言:javascript
复制
  #!python3
  import random
  misere=1 # 0 for "normal game", 1 for "misere game"
  if misere: print("Py Nim, misere game: don't take last token")
  else:      print("Py Nim, normal game: be the one to take last token")
  
  def get_tokens(tokens):
      take=0
      while (take < 1 or take > min(3,tokens)):
          take = int(input("How many tokens would you like to take? "))
  
      tokens = tokens - take
      print(f'You take {take} tokens.')
      print(f'{tokens} tokens remaining.\n')
      if tokens==misere:
          print ('You win!')
          return 0
      elif tokens==0:
          print('Strange way of losing, this was a misere game...')
          return 0
      else:
          return comp_turn(tokens)
  
  def comp_turn(tokens):
      take = (tokens-misere) % 4
      if take==0:
          take=random.randint(1,3)
      tokens = tokens - take
      print (f'Computer takes {take} tokens.')
      print (f'{tokens} tokens remaining.\n')
      if tokens==misere:
          print ('Computer wins!')
          return 0
      return tokens
  
  tokens = random.randint(12,16)
  print (f'We start with {tokens} tokens.')
  if (input("Should the computer play first? (y/n)").upper()=="Y"):
      tokens=comp_turn(tokens)
  while (tokens > 0):
      tokens=get_tokens(tokens)

如果你用多少个记号来回答电脑是否先玩,你总是可以赢的。

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

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

复制
相关文章

相似问题

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