Dante Davidson
Dante Davidson

Reputation: 29

Syncing a random variable in ruby

I am making a simple Tic tac toe game where the rows are represented by three arrays and columns are items in those arrays. For my computer choice I use two separate functions to get the row and column.

$first = ["-","-","-"]
$secound = ["-","-","-"]
$third = ["-","-","-"]
def get_row
    row = nil
    case rand(3)
    when 0
        row = $first
    when 1
        row = $secound
    else
        row = $third
    end
    row
end
def get_col
    col = nil
    case rand(3)
    when 0
        col = 0
    when 1
        col = 1
    else 
        col = 2
    end
    col
end

I then use a third function that keeps generating a new "guess" until it finds an empty spot, at which point it marks an 0.

def computer_guess
    temp = false
    try = get_row[get_col]
    while temp == false 
        if try == "-"
           get_row[get_col] = "O"
            temp = true 
        else
            try = get_row[get_col]
        end
    end
end

My problem is that i cant return the guess to the same position that i check for validity. Is the there a way to sync these or do i need a different approach?

Upvotes: 1

Views: 72

Answers (3)

Stefan
Stefan

Reputation: 114228

The problem with your approach is that get_row[get_col] returns a random element every time it is called, so likewise get_row[get_col] = "O" will set a random element to "O". You inspect one element and then set another.

You could fix this quickly by modifying the retrieved element in-place:

if try == "-"
  try.replace("O")
  # ...

But semantically, I don't like that fix very much. Thinking of a tic-tac-toe board, I'd rather assign an "O" to the free spot than transforming the existing placeholder from "-" into "O".

Did you notice that your get_row method returns an array whereas get_col returns an index? I think this mixture of arrays and indices makes your code a bit convoluted.

It's much easier (in my opinion) to access both, row and column via an index.

To do so, you could put your three rows into another array:

$board = [
  ['-', '-', '-'],
  ['-', '-', '-'],
  ['-', '-', '-']
]

The first row can be accessed via $board[0] and its first element via $board[0][0]. To set an element you'd use: $board[1][2] = 'O' (this sets the middle row's right-most element to "O"). Of course, you can also use variables, e.g. $board[row][col].

With this two-dimensional array, your computer_guess could be rewritten using just two random indices: (get_row and get_col aren't needed anymore)

def computer_guess
  loop do
    row = rand(3)                # random row index
    col = rand(3)                # random column index
    if $board[row][col] == '-'   # if corresponding spot is "-"
      $board[row][col] = 'O'     #    set that spot to "O"
      break                      #    and break out of the loop
    end
  end
end

Note however that "blindly" guessing spots until you find a free one might not be the best approach.

Instead, you could generate a list of "free" spots. This can be done by first generating an array of all coordinates1 and then select-ing those row/col pairs whose spot is "-":

def free_spots
  coordinates = [0, 1, 2].product([0, 1, 2])
  coordinates.select { |row, col| $board[row][col] == '-' }
end

Now you just have to chose a random pair (via sample) and set the corresponding spot to "O":

def computer_guess
  row, col = free_spots.sample
  $board[row][col] = 'O'
end

1 Array#product returns the Cartesian product of the given arrays. It's an easy way to get all pairs:

[0, 1, 2].product([0, 1, 2])
#=> [[0, 0], [0, 1], [0, 2],
#    [1, 0], [1, 1], [1, 2],
#    [2, 0], [2, 1], [2, 2]]

Upvotes: 1

Fernand
Fernand

Reputation: 1333

when there is only one available spot, it might take time to guess it.
So it might be better to collate the available spots and get a random value from there:

def computer_guess
  free_spots=[]
  free_spots.concat($first.each_with_index.map{|x,i|x=='-' ? ['$first',i] : nil}.compact)
  free_spots.concat($secound.each_with_index.map{|x,i|x=='-' ? ['$secound',i] : nil}.compact)
  free_spots.concat($third.each_with_index.map{|x,i|x=='-' ? ['$third',i] : nil}.compact)
  try=free_spots.sample
  eval"#{try[0]}[#{try[1]}]='O'" unless try.nil?
end

Upvotes: 1

user3309314
user3309314

Reputation: 2543

Here is another approach. This code imitates game computer against itself. You can easily replace one computer's move with user input.

ROWS = 3
COLUMNS = 3
MAX_TURNS = ROWS * COLUMNS

def random_cell
  { row: rand(ROWS), column: rand(COLUMNS) }
end

def empty_random_cell(board)
  while true
    cell = random_cell
    return cell if board_value(board, cell).nil?
  end
end

def board_value(board, cell)
  board[cell[:row]][cell[:column]]
end

def turn(board, cell, value)
  row = cell[:row]
  column = cell[:column]
  board[row][column] = value
end

def print_board(board)
  ROWS.times do |row|
    COLUMNS.times do |column|
      value = board[row][column]
      print value.nil? ? '_' : value
      print ' '
    end
    puts
  end
end

board = Array.new(ROWS) { Array.new(COLUMNS) }
print_board(board)
turns_counter = 0
while true
  cell = empty_random_cell(board)
  turn(board, cell, 'X')
  turns_counter += 1
  break if turns_counter == MAX_TURNS
  cell = empty_random_cell(board)
  turn(board, cell, '0')
  turns_counter += 1
  break if turns_counter == MAX_TURNS
end

print_board(board)

Upvotes: 1

Related Questions