【问题标题】:Minimax algorithm in Ruby in Object-Oriented wayRuby中面向对象的Minimax算法
【发布时间】:2021-06-05 15:38:23
【问题描述】:

我正在尝试以面向对象的方式为井字游戏实现极小极大算法。一旦算法确定了最佳移动,我有点不知道如何处理恢复我的board 对象的状态。在运行程序时,我注意到minimax方法对当前board对象的操作并不理想。

我添加了一个方法来撤消 minimax 方法所做的移动:board.[]=(empty_square, Square::INITIAL_MARKER)

我注意到算法做出了错误的选择。这里,X 是播放器,O 是计算机。如果这是board的状态:

     |     |
     |     |   
     |     |
-----+-----+-----
     |     |
     |  X  |   
     |     |
-----+-----+-----
     |     |
     |     |  O
     |     |

当玩家X移动并选择方格2时,minimax(计算机,O)将选择7而不是8,这将是一个更好的选择:

     |     |
     |  X  |   
     |     |
-----+-----+-----
     |     |
     |  X  |   
     |     |
-----+-----+-----
     |     |
  O  |     |  O
     |     |

由于我缺乏经验,我对如何进行有点迷茫,希望得到任何指导!

这里是minimax 方法:

  def minimax
    best_move = 0
    score_current_move = nil
    best_score = -10000 if @current_marker == COMPUTER_MARKER
    best_score = 10000 if @current_marker == HUMAN_MARKER
    board.unmarked_keys.each do |empty_square|
      board.[]=(empty_square, @current_marker)
      if board.full?
        score_current_move = 0
      elsif board.someone_won?
        score_current_move = -1 if board.winning_marker == HUMAN_MARKER
        score_current_move = 1 if board.winning_marker == COMPUTER_MARKER
      else
        alternate_player
        score_current_move = minimax[0]
      end
      if ((@current_marker == COMPUTER_MARKER) && (score_current_move >= best_score))
        best_score = score_current_move
        best_move = empty_square
      elsif ((@current_marker == HUMAN_MARKER) && (score_current_move <= best_score))
        best_score = score_current_move
        best_move = empty_square
      end
      board.[]=(empty_square, Square::INITIAL_MARKER)
    end
    [best_score, best_move]
  end

【问题讨论】:

  • 你能不能把你的问题浓缩一点,这样就不需要阅读和理解 350 行代码了?请尝试将其简化为显示您的问题的最小示例。然后准确描述您想要实现的目标,您当前的代码到底有什么不适用的(您是否看到不正确的行为、异常/错误,还有其他什么?)。请尽可能具体,并包含任何错误消息和示例代码以专门显示您的问题。请参阅stackoverflow.com/help/minimal-reproducible-example 了解一些指南。您可以使用下方的编辑链接编辑您的问题。
  • 您可能想考虑为您的类定义一个 initialize_copy 方法,这应该会改变 dup 的行为。

标签: ruby algorithm oop minimax minmax


【解决方案1】:

我认为在这里定义任何类都没有特别的优势。只有一个棋盘,只有两个玩家(机器和人类)的操作方式完全不同。

主要方法

接下来我会写main方法,它依赖于几个helper方法,它们都可以是private

def play_game(human_moves_first = true)
  raise ArgumentError unless [true, false].include?(human_moves_first)
  human_marker, machine_marker = 
    human_moves_first ? ['X', 'O'] : ['O', 'X']
  board = Array.new(9)

  if human_moves_first
    display(board)
    human_to_move(board, 'X')
  end

  loop do
    display(board)
    play = machine_best_play(board, machine_marker)   
    board[play] = machine_marker
    display(board)
    if win?(board)
      puts "Computer wins"
      break
    end
    if tie?(board)
      puts "Tie game"
      break
    end
    human_to_move(board, human_marker)
    if tie?(board)
      puts "Tie game"
      break
    end
  end
end          

如你所见,我提供了由机器或人工启动的选项。

最初,board9 nils 的数组。

该方法简单地循环,直到确定机器是赢还是平局。众所周知,机器在逻辑上是不会输的。在循环的每一次通过中,机器都会做一个标记。如果结果是胜利或平局,则游戏结束;否则人类被要求做一个标记。

在考虑machine_best_play 方法之前,让我们考虑一些需要的简单辅助方法。

简单的辅助方法

我将演示这些方法,board 定义如下:

board = ['X', 'O', 'X',
         nil, 'O', nil,
         nil, nil, 'X']

请注意,虽然人类将这九个位置称为 19,但在内部,它们表示为 board08 的索引。

确定未标记的单元格

def unmarked_cells(board)
  board.each_index.select { |i| board[i].nil? }
end
unmarked_cells(board)
  #=> [3, 5, 6, 7]

让人类做出选择

def human_to_move(board, marker)
  loop do
    puts "Please mark '#{marker}' in an unmarked cell"
    cell = gets.chomp
    if (n = Integer(cell, exception: false)) && n.between?(1, 9)
      n -= 1 # convert to index in board
      if board[n].nil?
        board[n] = marker
        break
      else
        puts "That cell is occupied"
      end
    else
      puts "That is not a number between 1 and 9"
    end
  end
end  
human_to_move(board, 'O')  
Please mark an 'O' in an unmarked cell

如果cell = gets.chomp #=&gt; "6" 那么

board
  #=> ["X", "O", "X", nil, "O", "O", nil, nil, "X"]

对于以下内容,我已将board 设置为上面的原始值。

显示板

def display(board)
  board.each_slice(3).with_index do |row, idx|
    puts   "     |     |"
    puts "  #{row.map { |obj| obj || ' ' }.join('  |  ')}"
    puts "     |     |"
    puts "-----+-----+-----" unless idx == 2
  end
end
display(board)
     |     |
  X  |  O  |  X
     |     |
-----+-----+-----
     |     |
     |  O  |   
     |     |
-----+-----+-----
     |     |
     |     |  X
     |     |

确定最后一步(机器还是人类)是否获胜

WINNING_CELL_COMBOS = [
  [0,1,2], [3,4,5], [6,7,8], [0,3,6], [1,4,7], [2,5,8], [0,4,8], [2,4,6]
]
def win?(board)
  WINNING_CELL_COMBOS.any? do |arr|
    (f = arr.first) != nil && arr == [f,f,f]
  end
end
win? board
  #=> false
win? ['X', nil, 'O', 'nil', 'X', 'O', nil, nil, 'X']
  #=> true
win? ['X', nil, 'O', 'nil', 'X', 'O', 'X', nil, 'O']
  #=> true

判断比赛是否平局

def tie?(board)
  unmarked_cells(board).empty?
end
tie?(board)
  #=> false
tie? ['X', 'X', 'O', 'O', 'X', 'X', 'X',  'O', 'O']
  #=> true

注意unmarked_cells.empty?可以替换为board.all?

使用极小极大算法确定机器的最佳播放

MACHINE_WINS = 0
TIE = 1
MACHINE_LOSES = 2
NEXT_MARKER = { "X"=>"O", "O"=>"X" }

def machine_best_play(board, marker)
  plays = open_cells(board)
  plays.min_by |play|
    board_after_play = board.dup.tap { |a| a[play] = marker }
    if machine_wins?(board_after_play, marker)
      MACHINE_WIN
    elsif plays.size == 1
      TIE
    else
      human_worst_outcome(board_after_play, NEXT_MARKER[marker]) 
    end
  end
end

这需要另外两个方法。

确定机器当前状态board的最佳最差结果@

def machine_worst_outcome(board, marker)
  plays = open_cells(board)
  plays.map |play|
    board_after_play = board.dup.tap { |a| a[play] = marker }
    if win?(board_after_play)
      MACHINE_WINS
    elsif plays.size == 1
      TIE
    else
      human_worst_outcome(board_after_play, NEXT_MARKER[marker]) 
    end
  end.min
end

确定当前董事会状态下人类的最佳最坏结果,假设 人类也玩极小极大策略

def human_worst_outcome(board, marker)
  plays = open_cells(board)
  plays.map |play|
    board_after_play = board.dup.tap { |a| a[play] = marker }
    if win?(board_after_play)
      MACHINE_LOSES
    elsif plays.size == 1
      TIE
    else
      machine_worst_outcome(board_after_play, NEXT_MARKER[marker])
    end
  end.max
end

请注意,从机器的角度来看,人类最大化最坏的结果,而机器最小化最坏的结果。

差不多了

剩下的就是消除所有存在的错误。如果你愿意的话,我现在时间不多,我会把它留给你。随时编辑我的答案以进行任何更正。

【讨论】:

  • 感谢您非常详细的回答! OOP 的挑战在于复制板状态不是很简单。我设法以功能方式实现算法,但不是 OOP 方式。只是好奇如何在 OOP 中完成。谢谢!我已经改进了我的算法并实现了一种撤消移动的方法。现在我看到我的算法只是选择了最好的移动,而不是最好的最坏的移动。
猜你喜欢
  • 2019-03-27
  • 2014-12-15
  • 2013-05-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-06-02
  • 1970-01-01
相关资源
最近更新 更多