My first encounter with Ruby has been quite wonderful. Even with the guidance by the Flatiron curriculum and the team there, it was a really big challenge for me. However, we know have a working game! I want to share my learnings below in hope that maybe it would help others - and even more so, for myself to keep going!
Below is my first attempt to rebuild a basic game of Tic Tac Toe.
HOW THE GAME WORKS
Two player starts with a blank board of 9 spaces on a 3 x 3 grid
Each play takes turn to place either an “X” or “O” (a marker) on the board
The game is won when one player achieve three markers in a row - horizontally, vertically, or diagonally
The game is draw when the board is full and there is no winning moves
The program needs to guide players along the way and announce the winner if game is won
SETTING UP THE GAME IN BIN
#!/usr/bin/env ruby
require_relative '../lib/tic_tac_toe'
puts "Welcome to Tic Tac Toe!"
board = [" ", " ", " ", " ", " ", " ", " ", " ", " "]
play(board)
SETTING UP THE BOARD & DEFINE WINNING COMBINATIONS
# -> lib/tic_tac_toe.rb
def display_board(board)
puts " # | # | # "
puts "-----------"
puts " # | # | # "
puts "-----------"
puts " # | # | # "
end
# TROUBLESHOOTING: This was easy to get wrong because of all the spacing and number of dashes. The test specs was written quite specifically so you should pay attention to the details.
DEFINE PLAYER’S MOVES AND CONVERT THEM TO INTEGERS
def input_to_index(user_input)
user_input.to_i - 1
end
def player_move(board, index, marker)
board[index] = marker
end
# TROUBLESHOOTING: I was amazed at how simple it is to convert a player’s input into integer ( .to_i) Super important: the use of = (assignment) is different then == (comparison). It took me 2 hours to figure out that I got an extra =.
CONDITIONS LOGIC TO CHECK IF POSITION IS TAKEN AND IF IT’S A VALID MOVE
def position_taken? (board, index)
if board[index] == "" || board[index] == " " || board[index] == nil
return false
else
return true
end
end
def valid_move?(board, index)
if !position_taken?(board, index) && (index).between?(0,8)
return true
else
return false
end
end
# TROUBLESHOOTING: The use of ! in front of !positiontaken?(board, index) reads as position is NOT taken. This is a better way to set condition vs. before I was using the positiontaken?(board, index) == false. Linking || (or) && (and) is also very helpful.
ASKING FOR PLAYERS’ INPUTS, KEEP TRACK OF THE TURNS AND PLAYER’S MARKERS
def current_player(board)
turn_count(board) % 2 == 0? "X" : "O"
end
def turn(board)
puts "Please enter 1-9:"
user_input = gets.strip
index = input_to_index(user_input)
if valid_move?(board, index)
player_move(board, index, current_player(board))
display_board(board)
else
turn(board)
end
end
def turn_count(board)
counter = 0
board.each {|space|
if space == "X" || space == "O"
counter += 1
end
}
counter
end
# TROUBLESHOOTING: Again I was amazed at how simple it is to write the second the last line above: Is turncount(board) is divisible by 2 -> return “X”, if not, return “O”. It’s important that the playermove includes the current_player(board) so as to display the marker “X” or “O”.
CONDITIONS LOGOC TO FIND OUT IF THE GAME IS WON, FULL, DRAW, OR OVER
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 |single_win_combo|
win_index_1 = single_win_combo[0]
win_index_2 = single_win_combo[1]
win_index_3 = single_win_combo[2]
position_1 = board[win_index_1]
position_2 = board[win_index_2]
position_3 = board[win_index_3]
if position_1 == position_2 && position_2 == position_3 && position_taken?(board, win_index_1)
return single_win_combo
end
end
return false
end
def full?(board)
if board.any? {|index| index == nil || index == " "}
return false
else
return true
end
end
def draw?(board)
if !won?(board) && full?(board)
return true
elsif!full?(board) && !won?(board)
return false
else won?(board)
return false
end
end
def over?(board)
if draw?(board) || won?(board) || full?(board)
return true
else
return false
end
end
# TROUBLESHOOTING: It’s important to think of position_1, position_2, position_3 as reusable and that they will cycle through all 9 spaces on the board. The elegance of comparing them to each other also got me. In draw?(board), the appearance and sequence of won?(board) and full?(board) matter.
SET UP FOR THE ENTIRE PLAY AND WINNER ANNOUNCEMENT
def winner(board)
if won?(board)
return board[won?(board)[0]]
end
end
def play(board)
counter = 0
until counter == 9
turn(board)
counter += 1
end
end
def play(board)
until over?(board)
turn(board)
end
if won?(board)
winner(board) == "X" || winner(board) == "O"
puts "Congratulations #{winner(board)}!"
else draw?(board)
puts "Cat\'s Game!"
end
end
# TROUBLESHOOTING: I had a hard time (still do) associating and differentiating won?(board)[0] and single_win_combo and position_1. I think an important take away for me is the difference between being INSIDE the method vs. ability to use output OUTSIDE of the method.
Reach out if you’re learning too and want to share.