DEV Community

loading...

Tic Tac Toe

Dorthy Thielsen
Full Stack Web Dev student at Flatiron
・4 min read

The first part of the Flatiron self paced program is all about Ruby. I knew absolutely nothing about Ruby and still struggle with it a bit. Part of the curriculum is all about making a Tic Tac Toe game. I can't remember how many of the labs are centered around Tic Tac Toe, but I definitely got sick of the game. I really struggled with parts of this challenge. I wanted to write about how I worked through the code as I think it really helps to explain to myself why this works.

First, I had to create the board by defining how I wanted the board to be visually displayed. Also creating a board array that assigned indexes to the squares on the board.

def display_board(board)
  puts " #{board[0]} | #{board[1]} | #{board[2]} "
  puts "-----------"
  puts " #{board[3]} | #{board[4]} | #{board[5]} "
  puts "-----------"
  puts " #{board[6]} | #{board[7]} | #{board[8]} "
end
Enter fullscreen mode Exit fullscreen mode

Then we had to get input from the user that would indicate where the player wanted to move. The player would indicate 1-9 where they wanted to move on the board. Since arrays start their index at 0, I had to minus one from whatever the player input to work correctly.

def input_to_index(user_input)
  user_input.to_i - 1
end
Enter fullscreen mode Exit fullscreen mode

Next was creating the the rules for the move and how turns would work. I had to check if the move would be valid. What makes a move valid? Well it must be associated with one of the indexes on the board, so say the player input 15, well that isn't valid since there is no space 15 on the board. Also, the spaces can't be occupied already. The thing I loved about this section was the use of && (and), || (or), and the bang (!) operator. Also gets.strip is such a cool method of getting user input and making sure there isn't any excess on it like an additional line.

def move(board, index, current_player)
  board[index] = current_player
end

def valid_move?(board, index)
  index.between?(0,8) && !position_taken?(board, index)
end

def turn(board)
  puts "Please enter 1-9:"
  input = gets.strip
  index = input_to_index(input)
  if valid_move?(board, index)
    move(board, index, current_player(board))
    display_board(board)
  else
    turn(board)
  end
end
Enter fullscreen mode Exit fullscreen mode

Now it was time to essentially figure out when the game was over. This included making a counter to indicate the total number of turns, only when a space was taken. The counter would go up, only if a valid input was put in. This was also helpful to use to determine is it was player X's turn or player O's turn. This was my first time using a single line syntax for a conditional.

def turn_count(board) 
  count =0
  board.each do |occupied|
    if occupied == "X" || occupied == "O"
    count += 1
    end
  end
  count
end

def current_player(board)
  turn_count(board) % 2 == 0 ? "X" : "O"
end

def position_taken?(board, index)
  !(board[index].nil? || board[index] == " ")
end
Enter fullscreen mode Exit fullscreen mode

The last section was to define how someone won the game. This was probably the most challenging section for me. I found myself constantly trying to call local variables outside of the method. First I had to define the winning combinations by noting all the indexes that would indicate three in a row on the board. The indexes also had to be filled with either three X's or three O's. How about if all the spots were filled but no one won? Well that needed to be accounted for too. This was a great chance to use the .all? function. If it is all full and not won, then it must be a draw. The game is over if the board is won, a draw, or full. While all of this code is very simple, mostly just one line, it was a challenge to think so abstractly about how to achieve all of these conditions.

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

def won?(board)
  WIN_COMBINATIONS.each do |win_combination|
    win_index_1 = win_combination[0] 
    win_index_2 = win_combination[1]
    win_index_3 = win_combination[2]

    if board[win_index_1] == "X" && board[win_index_2] == "X" && board[win_index_3] == "X"
      return win_combination

    elsif board[win_index_1] == "O" && board[win_index_2] == "O" && board[win_index_3] == "O"
      return win_combination
    end
  end
  return false
end

def full?(board)
  board.all? { |token| token== "X" || token=="O"} 
end

def draw?(board)
  full?(board) && !won?(board)
end

def over?(board)
  won?(board) || draw?(board) || full?(board)
end

def winner(board)
  if won?(board)
    return board[won?(board)[0]] 
  end
end

def play(board)
  until over?(board) do
    turn(board)
  end
  if won?(board)
    puts "Congratulations #{winner(board)}!"
  else draw?(board)
    puts "Cat's Game!"
  end
end
Enter fullscreen mode Exit fullscreen mode

One great take away from this lab was writing out instructions for myself. Essentially writing the steps I needed to take to achieve the goal. I found when I didn't write out the steps, I would become lost so quickly. I also learned the magic that is pry so I wasn't coding in the dark anymore. I also liked writing my thought process afterwards so I better understood how each piece of code worked. I also found myself going into a dark place when I got stuck and thinking I would never succeed. However, by writing this blog, it proves that I survived and conquered. There are so many ways to achieve this same outcome, which is so fascinating to me.

Discussion (0)