如何解决Ruby 中面向对象的极小极大算法
我正在尝试以面向对象的方式为井字游戏实现极大极小算法。一旦算法确定了最佳移动,我就对如何处理恢复 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
解决方法
我认为在这里定义任何类都没有什么特别的好处。只有一个棋盘,只有两个玩家(机器和人)的操作方式截然不同。
主要方法
接下来我将编写 main 方法,它依赖于几个辅助方法,所有这些方法都可以是 私有的。
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
如您所见,我提供了选择谁开始,机器或人。
最初,board
是一个由 9
nil
组成的数组。
该方法简单地循环,直到确定机器是赢还是平局。众所周知,机器按照逻辑运行,不会输。在循环的每一次循环中,机器都会做一个标记。如果结果是胜利或平局,则游戏结束;否则人类会被要求做一个标记。
在考虑方法 machine_best_play
之前,让我们考虑一些需要的简单辅助方法。
简单的辅助方法
我将使用定义如下的 board
演示这些方法:
board = ['X','O','X',nil,'X']
请注意,虽然人类将这九个位置称为 1
到 9
,但在内部它们表示为 board
、0
到 {{1} }.
确定未标记的单元格
8
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 #=> "6"
对于以下内容,我已将 board
#=> ["X","O","X","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,7],[2,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','X']
#=> true
判断比赛是否平局
win? ['X','O']
#=> true
def tie?(board)
unmarked_cells(board).empty?
end
tie?(board)
#=> false
注意 tie? ['X','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
请注意,从机器的角度来看,人类最大化最坏的结果,而机器最小化其最坏的结果。
快到了
剩下的就是消除所有存在的错误。如果你愿意的话,现在时间不够,我会把它留给你。请随时编辑我的答案以进行更正。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。