2

I was wondering if anyone could explain to me why I can't use my Hash($player_x_command_list) to access my player_x_move(position) method? What's the correct way of doing this?

What I am trying to do is, create a Hash that lets the user enter input and it reflecting back to my methods in my class.

I have tried various ways of altering so that the code could work correctly but nothing works.

I'm getting the error:

undefined method `player_x_move' for Game:Class (NoMethodError)

Does that mean hashes can't store methods?

Here's my code:

#Tic Tac Toe Game
#The format is the below: where index 0 represents top left and index 8 represents bottom right
#goes 0,1,2
#     3,4,5
#     6,7,8
#"e" is for empty. "x" is for Player X moves and "o" is for Player O moves



class Game
  @@player_x_win_count = 0
  @@player_o_win_count = 0

  def initialize
    @board = Array.new(9, "e")
    @move_number = 0
  end

  def get_names
  puts "Hi Welcome to my Tic Tac Toe Game. The board looks like this:

  |TL|TM|TR|
  |ML|MM|MR|
  |BL|BM|BR|

  Each position of the Tic Tac Toe board is represented by two letters. To \"X\" or \"O\" a position, just input the two letters in CAPS like \"MM\"


  The command list is as follows:
  TL = top left
  TM = top mid
  TR = top right
  ML = mid left
  MM = mid mid
  MR = mid right
  BL = bottom left
  BM = bottom mid
  BR = bottom right
  board = to view the board
  new game = to clean the board and create a new game (note that this command should only be used when you don't want to continue on the current game. The game automatically creates a new game if a winner, loser, or draw is declared)
   "
    puts "Please Enter PlayerX's name. He/she will be using X's to mark the board."
    @player_x = gets.chomp

    puts "Please Enter PlayerO's name. He/she will be using O's to mark the board."
    @player_o = gets.chomp

    self.new_round
  end

$player_x_command_list = {"TL" => self.player_x_move(0), "TM" => self.player_x_move(1), "TR" => self.player_x_move(2), "ML" => self.player_x_move(3), "MM" => self.player_x_move(4), 
    "MR" => self.player_x_move(5), "BL" => self.player_x_move(6), "BM" => self.player_x_move(7), "BR" => self.player_x_move(8), "board" => self.board, "new game" => self.clean_board, 
    "win count" => self.win_count}

$player_o_command_list = {"TL" => self.player_o_move(0), "TM" => self.player_o_move(1), "TR" => self.player_o_move(2), "ML" => self.player_o_move(3), "MM" => self.player_o_move(4), 
    "MR" => self.player_o_move(5), "BL" => self.player_o_move(6), "BM" => self.player_o_move(7), "BR" => self.player_o_move(8), "board" => self.board, "new game" => self.clean_board, 
    "win count" => self.win_count}


  def enter_command_player_x
    puts "Please input your command, #{@player_x} aka PlayerX"
    command = gets.chomp
    $player_x_command_list[command]
  end

  def enter_command_player_o
    puts "Please input your command, #{@player_o} aka PlayerY. Type \"help\" to see a full list of commands"
    command = gets.chomp
    $player_o_command_list[command]
  end


  def new_round
    puts "So who wants to go first this round"
    went_first_this_round = gets.chomp
    if went_first_this_round == @player_x
      self.enter_command_player_x
    elsif went_first_this_round == @player_o
      self.enter_command_player_o
    else
      puts "Not a valid name. Please enter one of the player's names"
    end
  end

  def board
    print "|#{@board[0]}|#{@board[1]}|#{@board[2]}|\n|#{@board[3]}|#{@board[4]}|#{@board[5]}|\n|#{@board[6]}|#{@board[7]}|#{@board[8]}|"
  end

  def player_x_move(position)
    if @board[position] == "x" || @board[position] == "o"  
      return "That move was invalid as someone has already moved there. Please enter a valid move"
    end
    @board[position] = "x"
    @move_number += 1
    puts "That was move number #{@move_number} and the current board looks like: " 
    self.board
    self.check_game
    puts "Now it is #{player_o}'s turn. #{player_o} please input your next command."
    self.enter_command_player_o
  end

  def player_o_move(position)
    if @board[position] == "x" || @board[position] == "o"     
      return "That move was invalid as someone has already moved there. Please enter a valid move"
    end
    @board[position] = "o"
    @move_number += 1
    puts "That was move number #{@move_number} and the current board looks like: "
    self.board
    self.check_game
    puts "Now it is #{player_x}'s turn. #{player_x} please input your next command"
    self.enter_command_player_x
  end

  def check_game
    triple_x = "xxx"
    triple_o = "ooo"
    if @move_number == 9
      @move_number = 0
      self.clean_board
      return "The board is completely filled up. Looks like this is a draw. This is Game Over. Make a new game by setting any variable = to new.Game and using that variable to play"
    elsif @board[0] + @board[1] + @board[2] == triple_x
      @@player_x_win_count += 1
      @move_number = 0
      self.clean_board
      return "Player X Wins"
    elsif @board[3] + @board[4] + @board[5] == triple_x
      @@player_x_win_count += 1
      @move_number = 0
      self.clean_board
      return "Player X Wins"
    elsif @board[6] + @board[7] + @board[8] == triple_x
      @@player_x_win_count += 1
      @move_number = 0
      self.clean_board
      return "Player X Wins"
    elsif @board[0] + @board[3] + @board[6] == triple_x
      @@player_x_win_count += 1
      @move_number = 0
      self.clean_board
      return "Player X Wins"
    elsif @board[1] + @board[4] + @board[7] == triple_x
      @@player_x_win_count += 1
      @move_number = 0
      self.clean_board
      return "Player X Wins"
    elsif @board[2] + @board[5] + @board[8] == triple_x
      @@player_x_win_count += 1
      @move_number = 0
      self.clean_board
      return "Player X Wins"
    elsif @board[0] + @board[4] + @board[8] == triple_x
      @@player_x_win_count += 1
      @move_number = 0
      self.clean_board
      return "Player X Wins"
    elsif @board[2] + @board[4] + @board[6] == triple_x
      @@player_x_win_count += 1
      @move_number = 0
      self.clean_board
      return "Player X Wins"
    #now check if Player O Wins
    elsif @board[0] + @board[1] + @board[2] == triple_o
      @@player_y_win_count += 1
      @move_number = 0
      self.clean_board
      return "Player O Wins"
    elsif @board[3] + @board[4] + @board[5] == triple_o
      @@player_y_win_count += 1
      @move_number = 0
      self.clean_board
      return "Player O Wins"   
    elsif @board[6] + @board[7] + @board[8] == triple_o
      @@player_y_win_count += 1
      @move_number = 0
      self.clean_board
      return "Player O Wins"   
    elsif @board[0] + @board[3] + @board[6] == triple_o
      @@player_y_win_count += 1
      @move_number = 0
      self.clean_board
      return "Player O Wins"    
    elsif @board[1] + @board[4] + @board[7] == triple_o
      @@player_y_win_count += 1
      @move_number = 0
      self.clean_board
      return "Player O Wins"   
    elsif @board[2] + @board[5] + @board[8] == triple_o
      @@player_y_win_count += 1
      @move_number = 0
      self.clean_board
      return "Player O Wins"  
    elsif @board[0] + @board[4] + @board[8] == triple_o
      @@player_y_win_count += 1
      @move_number = 0
      self.clean_board
      return "Player O Wins" 
    elsif @board[2] + @board[4] + @board[6] == triple_o
      @@player_y_win_count += 1
      @move_number = 0
      self.clean_board
      return "Player O Wins"   
    else
      return "no one has WON YET! Continue your GAME!!"
    end
  end

  def clean_board
    @board = Array.new(9, "e")
  end

  def win_count
    puts "So far Player X has won #{@@player_x_win_count} times and Player O has won #{@@player_o_win_count} times."
  end

end

a = Game.new
a.get_names
Andrew Marshall
  • 95,083
  • 20
  • 220
  • 214
JaTo
  • 2,742
  • 4
  • 29
  • 38
  • 2
    You should avoid using global variables whenever possible and instead encapsulate them into an object. – Andrew Marshall Jul 13 '13 at 20:31
  • Thanks for the help so far guys. just one last simple question if anyone has time? In my code, I realized a lot of the ".self" are optional. Is it better/more efficient for my code to take it all out? Is there any ".self" that is actually required? – JaTo Jul 14 '13 at 16:42
  • Avoid qualifying a method call with `self` unless it’s needed. This is especially true as private methods cannot be called with a qualifying `self`. The only case in which `self` is usually required is when there is a local variable with the same name as the method being called. – Andrew Marshall Jul 14 '13 at 17:24

2 Answers2

4

You have a few things wrong here:

  1. Your scope is wrong. When defining the hash, self is the class, not the instance of the class that you want to operate on.
  2. When you create the hash, the value of the hash is going to be return value of the player_x_move etc method at the time it was defined.

You can simplify this a lot by using case.

def enter_command
  puts "Please input your command, #{@player_x} aka PlayerX"
  command = gets.chomp
  case command
  when "TL"
    player_x_move(0)
  when "TM"
    player_x_move(1)
  # etc
  else
    puts "#{command} is not a valid command."
  end
end

You can also simplify your code further by creating methods that accept parameters like enter_command, player_move, and then passing the player to operate on to them. This prevents you from having to duplicate each of your methods for each player.

Something else you could consider is just looking up the index of the move based on the command given:

COMMAND_POSITIONS = %w(TL TM TR ML MM MR BL BM BR)
def enter_command(player)
  puts "Please input your command, #{player}"
  command = gets.chomp
  case command
  when *COMMAND_POSITIONS
    player_move player, COMMAND_POSITIONS.index(command)
  when "board"
    board
  when "new game"
    clean_board
  when "win count"
    win_count
  else
    puts "#{command}" is not a valid command
  end
end
Chris Heald
  • 61,439
  • 10
  • 123
  • 137
3

Hashes can not store methods. Methods aren't objects, but can be converted to Procs. Method definitions return nilchanged since this writing. However, you can store a Proc or a lambda. You can also store the return (evaluation) of a method.

Here is an example of how you can store a Proc to a Hash that was derived from a method.

>> def hello(name)
>>   "Hello #{name}!"
>> end
=> nil
>> my_stored_methods = {:hello => method(:hello).to_proc} 
=> {:hello=>#<Proc:0x816f604 (lambda)>}
>> my_stored_methods[:hello].call("World")
=> "Hello World!"

Do not let the stroop effect of me calling the hash "my_stored_methods" lead you to believe that there is actually a real method stored there. It is lambda (a specialized Proc) stored in the hash, and used as appropriate. Indeed, had I not used .to_proc there, it would hold a Method instance.

Also, this solution does not grow with the development of the open method, if the method were to change, the Proc stored in the hash would continue to work as the method did at the point when the method was stored as a Proc.

As @AndrewMarshall reminds me, I could have left it as a Method instance. This still will not "store" the method itself, as when the method changes, the result will still be the historical behavior of the source method as it was when stored. It also provides for a stronger "Stroop effect" in that you may mistakenly think that an actual method is stored there. It simply is not.


Community
  • 1
  • 1
vgoff
  • 10,980
  • 3
  • 38
  • 56
  • 5
    -1: Calling `"foo".method(:length)` returns an instance of the [`Method` class](http://www.ruby-doc.org/core-2.0/Method.html), so in this sense methods are objects. – georgebrock Jul 13 '13 at 19:48
  • Sure, but give me the object ID of the method. – vgoff Jul 13 '13 at 20:57
  • http://stackoverflow.com/a/2602485/485864 They are not objects, in the same way as any other object is. But can be made to be. – vgoff Jul 13 '13 at 21:03
  • And of coruse instances of the Method class are objects. That is not what I said. So I think that is a slight of hand misdirection, in a way. @georgebrock – vgoff Jul 13 '13 at 21:06
  • @undur_gongor of course, if you are going to refer to the symbol that represents the name of the method, you are going to have success. That is not the same as storing the method in a hash. – vgoff Jul 13 '13 at 21:09
  • 1
    @vgoff You started by saying "Hashes cannot store methods", and since a `Hash` can contain `Method` instances I disagreed, but I guess there is technically a distinction between a method and the corresponding `Method` instance so we're probably both right (I'd remove the -1, but SO won't let me change my mind without an edit to the answer). – georgebrock Jul 13 '13 at 21:12
  • I think @georgebrock did put it correctly as well. Just does not change the facts. It is a different thing. One is an instance of a class, the other is a method. – vgoff Jul 13 '13 at 21:16
  • Yes, agreed. I removed my downvote. But for the question, storing Method objects in a hash is exactly what was intended. – undur_gongor Jul 13 '13 at 21:19
  • 1
    “[Don’t] believe that there is actually a real method stored there. It is lambda (a specialized Proc) stored in the hash”, well, yes, *but only because you called `to_proc` on it*. If you hadn’t done that, a Method object would have been stored there just fine. – Andrew Marshall Jul 13 '13 at 22:58
  • Yes, @AndrewMarshall. Still not an actual method though. But a method object. Either way, you still use .call to get to the invocation. – vgoff Jul 13 '13 at 23:49