【问题标题】:How can I DRY out this Ruby code in my Tic-Tac-Toe game? (Uses a 1.times block!)如何在我的井字游戏中干掉这段 Ruby 代码? (使用 1.times 块!)
【发布时间】: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


【解决方案1】:

只需将1.times 块内的代码重构为它自己的方法。而不是break if move,而是以同样的方式使用显式return运算符:return if move

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-01-15
    • 2014-01-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多