调用“box#{i} = ...”后,为什么“box1”未定义?

时间:2015-09-22 04:10:05

标签: ruby

我正在尝试制作一个井字游戏。我只列出了目前为止所获得的相关课程和方法的代码:

class Box
  attr_reader :name, :row, :column
  attr_accessor :is_marked, :contents
  def initialize(name, row, column, is_marked=false, contents)
    @name = name
    @row = row
    @column = column
    @is_marked = is_marked
    @contents = contents
  end

  def self.display_box
    print '|#{contents}|'
  end

end


#generate box instances
(1..9).each do |i|
  if i > 3
    col = i % 3
  else
    col = i
  end

  box#{i} = Box.new('box#{i}', (i/3).ceil, col, false, '_')
end

board = [[box1, box2, box3], [box4, box5, box6], [box7, box8, box9]]

def display_board
  box1.display_box; box2.display_box; box3.display_box; print '\n'
  box4.display_box; box5.display_box; box6.display_box; print '\n'
  box7.display_box; box8.display_box; box9.display_box; print '\n'
end

display_board

我无法弄清楚为什么创建我的类的实例会引发错误。错误是:

undefined local variable or method `box1' for <Context:0x000000024df8a8>  
(repl):44:in display_board'
(repl):61:in initialize'

我尝试在display_box方法中使用和不使用'self'运行它,同样的错误。

4 个答案:

答案 0 :(得分:3)

问题在于这一行。

box#{i} = Box.new('box#{i}', (i/3).ceil, col, false, '_')

您似乎正在尝试创建一组名为box1box2的变量,...在红宝石中可能还有其他方法可以做到这一点,但是那个&#39;不是吗你可以用eval来做,但你不想这样做。正如您从其余代码中可以看到的那样,处理大量变量就很烦人了。

而是制作一个盒子数组。

boxes[i] = Box.new("box#{i}", (i/3).ceil, col, false, '_')

您需要在循环之前声明boxes = []

然后使用范围从该列表创建您的电路板。

board = [boxes[1..3], boxes[4..6], boxes[7..9]]

display_board变得不那么重复了。

def display_board(board)
  board.each { |row|
    row.each { |box|
      box.display_box
    }
    puts "\n"
  }
end

最后,如果要在字符串中插入变量,则必须使用"。例如,在display_box

def display_box
  print "|#{contents}|"
end

答案 1 :(得分:2)

这是一个错误:

box#{i}

您可以插入字符串(和正则表达式),但变量名称不是a 串。字符串有引号。

即使您可以插入变量名称,也绝不会这样做:

box#{i} = Box.new('box#{i}', (i/3).ceil, col, false, '_')

相反,您将创建一个Array并将实例添加到Array:

boxes = []

(1..9).each do |i|
  if i > 3
    col = i % 3
  else
    col = i
  end

  boxes << Box.new('box#{i}', (i/3).ceil, col, false, '_')
end

然后,您的实例的名称是方框[0],方框[1]等

您还需要知道def创建了一个新范围,并且def内部的变量无法在def中看到。因此,在def内部无法看到在def外部创建的任何box1,box2等变量。您需要做的是将Array框传递给display_board()方法,如下所示:

def display_board(board_boxes)
   ...
end

display_board(boxes)

Ruby然后将您的方法调用和方法标题排列如下:

    display_board(boxes)
                    |
                    V
def display_board(board_boxes)

并将数组分配给名为board_boxes的变量:

board_boxes = boxes

然后在def中,board_boxes将是包含框的数组。

不要使用半冒号在一行上组合代码行。使用数组存储盒子实例时,可以显示如下框:

board_boxes.each do |box|
    box.display_box
end

如果您想在每三个方框后打印换行符,可以执行以下操作:

count = 1

board_boxes.each do |box|
  box.display_box
  print "\n" if count%3 == 0
  count += 1
end

答案 2 :(得分:0)

正如其他用户所提到的,你可以创建一个数组来保存Box的实例。

如果您已设置使用变量,则可以使用instance_variable_set,尽管使用数组可能更好。有关详细信息,请参阅here

使用instance_variable_set:

class Box
  attr_reader :name, :row, :column
  attr_accessor :is_marked, :contents
  def initialize(name, row, column, is_marked=false, contents)
    @name = name
    @row = row
    @column = column
    @is_marked = is_marked
    @contents = contents
  end

  def display_box
    print '|#{contents}|'
  end

end


#generate box instances
(1..9).each do |i|
  if i > 3
    col = i % 3
  else
    col = i
  end
instance_variable_set("@box#{i}", Box.new('box#{i}', (i/3).ceil, col, false, '_'))
end

board = [[@box1, @box2, @box3], [@box4, @box5, @box6], [@box7, @box8, @box9]]

def display_board
  @box1.display_box; @box2.display_box; @box3.display_box; print '\n'
  @box4.display_box; @box5.display_box; @box6.display_box; print '\n'
  @box7.display_box; @box8.display_box; @box9.display_box; print '\n'
end

display_board

使用数组:

class Box
  attr_reader :name, :row, :column
  attr_accessor :is_marked, :contents
  def initialize(name, row, column, is_marked=false, contents)
    @name = name
    @row = row
    @column = column
    @is_marked = is_marked
    @contents = contents
  end

  def display_box
    print '|#{contents}|'
  end

end


#generate box instances
arr = [];
(1..9).each do |i|
  if i > 3
    col = i % 3
  else
    col = i
  end
  name = 'box' + i.to_s
  arr.push(Box.new(name, (i/3).ceil, col, false, '_'))
end

board = [[arr[0], arr[1], arr[2]], [arr[3], arr[4], arr[5]], [arr[6], arr[7], arr[8]]]

def display_board(arr)
  arr[0].display_box; arr[1].display_box; arr[2].display_box; print '\n'
  arr[3].display_box; arr[4].display_box; arr[5].display_box; print '\n'
  arr[6].display_box; arr[7].display_box; arr[8].display_box; print '\n'
end

display_board(arr)

答案 3 :(得分:0)

您获得undefined local variable or method `box1',因为未定义box1

显然,您希望此行创建box1

box#{i} = ...

但实际上就是这样:

box

因为#开始发表评论。

双引号strings允许插值,但不能像这样插入变量名。

如何解决?

动态创建变量几乎总是一个坏主意。如果您有大量或不同数量的值,则应将它们存储在一个集合中 - ArrayHash或自定义值。

框对象

设置框非常复杂,因为您要将名称,行和列传递给每个框。一个盒子真的必须知道它在董事会中的位置吗?想想其他对象是更大结构的一部分:数组元素不知道它的索引(数组没有),字符不知道它的位置(字符串没有),哈希值不知道它的键(哈希确实)。

让我们从Box中删除这些额外的属性,让它只存储其内容:

class Box
  attr_accessor :content
  def initialize
    @content = '_'
  end

  def marked?
    content != '_'
  end
end

b = Box.new     #=> #<Box:0x007fab7a9f58a8 @content="_">
b.content       #=> "_"
b.marked?       #=> false
b.content = 'X'
b.marked?       #=> true

董事会对象

要创建一个3×3的盒子数组,我们可以写:(我已从输出中删除了对象ID)

board = Array.new(3) { Array.new(3) { Box.new } }
#=> [[#<Box @content="_">, #<Box @content="_">, #<Box @content="_">],
#    [#<Box @content="_">, #<Box @content="_">, #<Box @content="_">],
#    [#<Box @content="_">, #<Box @content="_">, #<Box @content="_">]]>

可以通过以下方式访问单个框:(索引从零开始)

board[0][0] #=> #<Box @content="_">

但由于board是如此重要的对象,我会编写一个自定义类:

class Board
  def initialize
    @boxes = Array.new(3) { Array.new(3) { Box.new } }
  end

  def box(x, y)
    @boxes[x][y]
  end
end

该类不公开@boxes,因为我们不希望更改数组。它提供了一种方法Board#box,它返回给定xy坐标的框。

格式

在我看来,BoxBoard都不应该包含处理格式化的代码,所以让我们编写一个小实用程序类来处理这一部分:

class BoardFormatter
  def draw(board)
    template.gsub(/[0-8]/) do |n|
      x, y = n.to_i.divmod 3
      board.box(x, y).content
    end
  end

  def template
    <<-STR
 0 | 1 | 2
---+---+---
 3 | 4 | 5
---+---+---
 6 | 7 | 8
    STR
  end
end

BoardFormatter#template返回董事会的字符串。您之前可能没有看到<<-:它被称为heredoc并在给定的STR分隔符之间创建多行字符串:

formatter = BoardFormatter.new
formatter.template #=> " 0 | 1 | 2\n---+---+---\n 3 | 4 | 5\n---+---+---\n 6 | 7 | 8\n"

BoardFormatter#draw使用String#gsub将每个数字([0-8])替换为相应的“内容”。

divmod是此处的关键,它将a / ba % b作为数组返回。因为我们使用从零开始的索引,所以这些只是第n个框的框坐标:

0.divmod(3) #=> [0, 0]
1.divmod(3) #=> [0, 1]
2.divmod(3) #=> [0, 2]
3.divmod(3) #=> [1, 0]
4.divmod(3) #=> [1, 1]
5.divmod(3) #=> [1, 2]
6.divmod(3) #=> [2, 0]
7.divmod(3) #=> [2, 1]
8.divmod(3) #=> [2, 2]

完整示例:

board = Board.new
board.box(0, 2).content = 'X'
board.box(0, 0).content = 'O'
board.box(2, 0).content = 'X'
board.box(1, 1).content = 'O'

formatter = BoardFormatter.new
puts formatter.draw(board)

输出:

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

基于单一的指数

玩家应该在玩游戏时输入一个基于索引的指数。处理用户输入时应处理此转换。在核心代码中,您应该始终使用从零开始的索引,就像Ruby一样。