【发布时间】:2017-04-09 10:23:58
【问题描述】:
我想知道是否有人可以帮我解决这个问题...我敢肯定这是打额头的事情之一...但我不知道如何擦干。
我正在制作一个井字游戏程序作为学习练习。我实际上已经完成了,但是有一点让我感到困扰,因为它看起来很笨拙。当我为计算机的移动做人工智能时,这部分就出现了。它的工作方式是,您测试八个条件中的每一个以寻找可能的移动。你看第一个条件;如果它导致移动,则返回该移动并跳过其余条件。如果八个条件中没有一个能引起棋子,则游戏结束(棋盘已满)。
嗯,条件很复杂,每个条件都需要单独的方法。所以,为了执行逻辑,我简单地说1.times(叹气),然后在一个单独的块中,一个一个地完成这些方法。在检查该方法是否返回移动之后,我说break if move,即跳出块。很明显,我不应该在 每个 项之后使用break if move。违反 DRY。我曾想过使用if...elsif... 或switch 语句,但这行不通,因为所有方法都必须经过,直到其中一个返回移动,并且这些方法不只是返回 true 或 false ;他们实际上返回移动的索引号或false。
(unless skip_rule == n 位是降低 AI 的一种方法;如果玩家希望 AI 可被击败,另一种方法会选择跳过规则。)
如果您想深入了解,请在此处查看第 96+ 行的代码:https://github.com/globewalldesk/tictactoe2/blob/master/lib/board.rb
# Test each set of conditions, until a move is found
move = false
1.times do
# Win: If you have two in a row, play the third to get three in a row.
# puts "Trying 1"
move, length = are_there_two_tokens_in_a_row(@ctoken) unless skip_rule == 0
break if move # skip to end if move is found
# Block: If the opponent has two in a row, play the third to block them.
# puts "Trying 2"
move, length = are_there_two_tokens_in_a_row(@ptoken) unless skip_rule == 1
break if move
# Fork: Create an opportunity where you can win in two ways (a fork).
# puts "Trying 3"
move = discover_fork(@ctoken) unless skip_rule == 2
break if move
# Block Opponent's Fork: If opponent can create fork, block that fork.
# puts "Trying 4"
move = discover_fork(@ptoken) unless skip_rule == 3
break if move
# Center: Play the center.
# puts "Trying 5"
unless skip_rule == 4
move = 4 if @spaces[4].c == " " # if the center is open, move there
end
break if move
# Opposite Corner: If the opponent is in the corner, play the opposite corner.
# puts "Trying 6"
move = try_opposite_corner unless skip_rule == 5
break if move
# Empty Corner: Play an empty corner.
# puts "Trying 7"
move = try_empty_corner
break if move
# Empty Side: Play an empty side.
# puts "Trying 8"
move = play_empty_side
# If move is still false, game is over!
end # of "do" block
# Make the change to @spaces; this edits the individual space and hence also
# the triads and the board, which use it.
@spaces[move].c = @ctoken if move
end # of computer_moves
更新(2016 年 12 月 3 日):我不太能理解下面得到的建议。但是一位离线的朋友看到了这个问题,并建议我制作一个方法数组并对其进行迭代……这就是我的手敲我额头的声音。我知道有一种(相对)简单的方法可以做到这一点。无论如何,在 Ruby 中可能无法使用方法数组,但可以创建包含方法的 proc 数组。所以这就是我所做的。这是更正/改进的代码:
# Initialize array of procs for each step of algorithm
rules = [
# Win: If you have two in a row, play the third to get three in a row.
Proc.new { are_there_two_tokens_in_a_row(@ctoken) },
# Block: If the opponent has two in a row, play the third to block them.
Proc.new { are_there_two_tokens_in_a_row(@ptoken) },
# Fork: Create an opportunity where you can win in two ways (a fork).
Proc.new { discover_fork(@ctoken) },
# Block Opponent's Fork: If opponent can create fork, block that fork.
Proc.new { discover_fork(@ctoken) },
# Center: Play the center.
Proc.new {
unless skip_rule == 4
move = 4 if @spaces[4].c == " " # if the center is open, move there
end
},
# Opposite Corner: If the opponent is in the corner, play the opposite corner.
Proc.new { try_opposite_corner },
# Empty Corner: Play an empty corner.
Proc.new { try_empty_corner },
# Empty Side: Play an empty side.
Proc.new { play_empty_side }
]
# Iterates over rule procs, and breaks out of iteration when move != false
(0..7).each do |rule_index|
move, length = rules[rule_index].call unless skip_rule == rule_index
break if move
end
很漂亮,n'est-ce pas?据我所知,这段代码已经干涸。 Here 是程序的更新源文件。顺便说一句,该程序可以设置为无与伦比,但用户可以让它“忘记”随机规则,使其可以击败。
感谢所有回复的人。下次我会尝试 Code Review.SE。
【问题讨论】:
-
一个很好解释的问题。它似乎很适合 Stack Overflow 的姊妹网站Code Review.SE。
-
你的
break if*variable-just-assigned-for-no-reason` 的东西真让人生气。别那样做。只需使用简单的条件执行if。您返回两件事的其他方法同样很难使用,并且在这里造成了很多摩擦。写的东西可以整齐地串起来。 -
您在这里寻找的是使用the Strategy Pattern 之类的东西,您会看到它经常在游戏中使用。将规则定义为一个简单的
lambda,它接受单个状态对象并在他们想要对其做某事时返回被操纵的状态,否则nil如果它们不适用。这将使迭代规则并确定最佳规则变得容易。它还可以使用sort这样简单的方式评估 N 条规则并选择最佳结果。 -
在@RJHunter 的建议的基础上,SO 关注损坏的代码修复,这通常会导致大量的更新,以及询问如何获得某些结果的问题(“给定这个数组,我怎么能产生这个哈希“?);相比之下,代码审查就是改进工作代码。看看那里的一些问答。你会印象深刻的。
标签: ruby dry tic-tac-toe