Search code examples
rubyclassinstance-variablesclass-methodinstance-methods

Questions about instance methods in ruby.


I have an assignment to build a tic-tac-toe game using ruby classes.

I'm new to programming and self taught so far, but was accepted into a program and need to polish up on some concepts to be ahead of the game.

I understand most concepts and got the majority of spec files to work for my game, but I did need to reference the solution code a few times to help solve the last bit of the problem.

  1. In creating the board class, two methods were created that were defined using empty arrays. I have never seen this before and wanted to know what was going on. When I went into my IDE and created a method with the name [](empty array) and two parameters, I'd get an error (syntax error, expecting end of input) trying to run it. It seems this must be something you can only do while setting up a class?

    I'm assuming these are getter and setter methods?

      def [](pos)
        row, col = pos
        grid[row][col]
      end
    
      def []=(pos, value)
        row, col = pos
        grid[row][col] = value
      end
    
  2. In the above code, grid is an instance variable of the board class. Why can you use an instance variable in a method without putting the "@" symbol in front of it - (@grid, instead of grid). This is a little confusing because with normal methods, if you define a variable outside of the method, the method doesn't recognize it. Why is it different inside of classes?

    #example 
    
    x = 6 
    
    def add(b)
      x + b
    end 
    
    => will return an error - local variable x is undefined.
    

Below is the full board class in case it'll make better sense understanding and answering the questions. Thank you!

class Board
  attr_reader :grid, :marks

  def initialize(grid = [])
    if grid.empty?
      @grid = Array.new(3) { Array.new(3) }
    else
      @grid = grid
    end
    @marks = [:X, :O]
  end

  def place_mark(pos, mark)
    grid[pos[0]][pos[1]] = mark
  end

  def [](pos)
    row, col = pos
    grid[row][col]
  end

  def []=(pos, value)
    row, col = pos
    grid[row][col] = value
  end

  def empty?(pos)
    grid[pos[0]][pos[1]].nil? ? true : false
  end

  def winner
    (grid + grid.transpose + diagnol).each do |win|
      return :X if win == [:X,:X,:X]
      return :O if win == [:O,:O,:O]
    end
    nil
  end

  def diagnol
    diag1 = [[0, 0], [1, 1], [2, 2]]
    diag2 = [[0, 2], [1, 1], [2, 0]]

    [diag1, diag2].map do |diag|
      diag.map {|row, col| grid[row][col]}
    end
  end

  def over?
    return true if grid.flatten.compact.size == 9 || winner
    return false if grid.flatten.compact.size == 0 || winner.nil?
  end


end

Solution

    1. Yes, that is a getter and setter for an array object on Board. (FWIW I think that it's poor Ruby style to do this, so I wouldn't use this in the future in your own code. I think that's why your IDE is having issues with this method as well.
    2. You can use grid without the @ because of the attr_reader declaration at the top of your class. This creates a read-only variable for the instance variable that is public on the class. It effectively creates a method called:

      def grid
        @grid
      end
      

      You would not be able to do grid = [] because attr_reader only creates a read only method. To create a read-write accessor, you would use attr_accessor. Keep in mind, using attr_ methods make the variable in question public on the class, so if you wanted to keep something internal, you would not use them.