Ruby - Basic Tutorial 03-23-2013, 02:45 PM
#1
Today you're going to make a Tic Tac Toe game by fellowing the steps that i'm going to be sharing with you.
Lets start by defining our initialize method, the first thing we want to do is create a container to hold the 9 places on the tic tac toe board. It is a 3 by 3 grid so we will name them A,B,C across and 1,2,3 down. Lets store them in a Ruby hash object with their default values set to spaces indicating they are empty:
Now that we have defined the 9 slots, lets define the 8 different possible winning columns. These are the slot (the 3 horizontal, 3 vertical and 2 diagnal) combinations a player must occupy to win the game. We will store these in an array called @columns where each index is a nested array defining the positions needed to win.
Next we want to randomly determine who is X and who is O and assign each to a variable called @cpu and @user:
Now lets give each player a name; lets name cpu as "Ruby" and ask the user to input their name and store each in the variables @cpu_name and @user_name respectively:
You will notice I called two methods here that are not yet defined; put_line and put_bar these are just convenience methods to print a line or a bar. We will define those methods shortly. For the last part of the initialize method we will let whoever is X make their first move:
That concludes the initialize method, the user_turn and cpu_turn methods are what we will call to allow either the user or the cpu make a move. We will define those later; for now, lets define the put_line and put_bar methods:
Now we have enough information to define the method that draws our tic tac toe board. Lets also print out the user name and who is X and who is O to make it easier for the player:
Remember, the @places hash defaults to having a space for each slot. When a player makes a move it will then contain it (X or O) instead, automatically drawing it to the board the next time we render it. Now lets define the cpu_turn method:
The first thing happening in this method is we call another function called cpu_find_move (we will define it later) which analyses the positions available to determine the best move. We then assign the returned value to the @places hash and output the move notifying the user what move was just placed. Finally we call a soon to be defined method called check_game which checks to see if anyone has won or if it is a stalemate. This method expects a parameter letting it know whose turn is next if the game is not over. Before we define the cpu_find_move method lets first define two functions that it relies on to analyse the board. The first function is called times_in_column which expects 2 parameters, the first being an array which is the column (of the columns in the @columns array) on the board we want to analyse and the second is what we are looking for (either X or O); and it will return how many times the item is in the column:
You will notice we are checking if any of the slots in the column are either a space or the item we are looking for. If they are neither then the only thing it can possibly be is the other player (X or O). And since we cannot win on a column that is occupied by the other player we instantly return 0. The next function we will define is called empty_in_column this one only expects one parameter which is also a column reference and just returns the first empty slot it finds in the column:
Now that we have both of those defined we can now define the cpu_find_move method. The first thing we will want to do is determine if there is any move possible that will cause a win and if so, return it. You can determine this by checking to see if any of the @columns that define a win already contain 2 of cpu's moves which indicates a 3rd move into that column will be a win:
If there is no moves available that will cause a win, the next thing to look for is if there are any moves available that will cause a loose. What I mean is, if there is a place the user can move to win. If there is, we need to move there first to block it:
If neither of these find anything then there is no possible winning moves either way. So what we want to do now is build up to a winning move. To do this we check to see if any column has at least one of cpu's moves so we can add to it:
If that didn't find anything either, then at this point we can just move to any empty slot. To make it seem more natural lets try to find a random empty slot, but if our first random slot is not empty, then at that point lets just move to the first empty slot we find:
That is the final part of the cpu_find_move function. So we can move on to creating the user_turn method. What we want to do here is draw the board so the user can decide where to move and then ask them to type in the slot they would like to place a move. We need to check their input to make sure it is either a valid move or the word "exit." If it is a valid move, make the move, or if it is "exit" then exit the program otherwise notify them to try again:
We already know we still need to define the check_game method, we also now need to define the wrong_move and wrong_input methods. If the user input a valid slot but it is not empty then we call wrong_move but if it is not a valid slot at all then we call wrong_input. We define these methods like so:
Now we can proceed to creating the check_game method. first we will want to see if the cpu or the user has won, if so, output who won and exit the game. If not, check to see if there are any moves left and call the appropriate players turn function, otherwise, output that the game is over and it is a draw. Here is the method definition:
There is now only one method left to define; the moves_left method that determines if it is a stalemate or not. Here it is:
And there you have it, you can now execute this program on the command line and it will play a game of tic tac toe with you. Like I said, I just started playing with Ruby, so if you see I did something wrong or find a way it could be improved, I hope you enjoyed the tutorial.
Lets start by defining our initialize method, the first thing we want to do is create a container to hold the 9 places on the tic tac toe board. It is a 3 by 3 grid so we will name them A,B,C across and 1,2,3 down. Lets store them in a Ruby hash object with their default values set to spaces indicating they are empty:
Code:
@places = {
"a1"=>" ","a2"=>" ","a3"=>" ",
"b1"=>" ","b2"=>" ","b3"=>" ",
"c1"=>" ","c2"=>" ","c3"=>" "
}
Now that we have defined the 9 slots, lets define the 8 different possible winning columns. These are the slot (the 3 horizontal, 3 vertical and 2 diagnal) combinations a player must occupy to win the game. We will store these in an array called @columns where each index is a nested array defining the positions needed to win.
Code:
@columns = [
['a1','a2','a3'],
['b1','b2','b3'],
['c1','c2','c3'],
['a1','b1','c1'],
['a2','b2','c2'],
['a3','b3','c3'],
['a1','b2','c3'],
['c1','b2','a3']
]
Next we want to randomly determine who is X and who is O and assign each to a variable called @cpu and @user:
Code:
@cpu = rand() > 0.5 ? 'X' : 'O'
@user = @cpu == 'X' ? 'O' : 'X'
Now lets give each player a name; lets name cpu as "Ruby" and ask the user to input their name and store each in the variables @cpu_name and @user_name respectively:
Code:
@cpu_name = "Ruby"
put_line
puts " RUBY TIC TAC TOE"
puts " What is your name?"
STDOUT.flush
@user_name = gets.chomp
put_bar
You will notice I called two methods here that are not yet defined; put_line and put_bar these are just convenience methods to print a line or a bar. We will define those methods shortly. For the last part of the initialize method we will let whoever is X make their first move:
Code:
if(@user == 'X')
user_turn
else
cpu_turn
end
That concludes the initialize method, the user_turn and cpu_turn methods are what we will call to allow either the user or the cpu make a move. We will define those later; for now, lets define the put_line and put_bar methods:
Code:
def put_line
puts "-----------------------------------------------------------------------------"
end
def put_bar
puts "#############################################################################"
puts "#############################################################################"
end
Now we have enough information to define the method that draws our tic tac toe board. Lets also print out the user name and who is X and who is O to make it easier for the player:
Code:
def draw_game
puts ""
puts "#{@cpu_name}: #{@cpu}"
puts "#{@user_name}: #{@user}"
puts ""
puts " a b c"
puts ""
puts " 1 #{@places["a1"]}|#{@places["b1"]}|#{@places["c1"]}"
puts " -----"
puts " 2 #{@places["a2"]}|#{@places["b2"]}|#{@places["c2"]}"
puts " -----"
puts " 3 #{@places["a3"]}|#{@places["b3"]}|#{@places["c3"]}"
end
Remember, the @places hash defaults to having a space for each slot. When a player makes a move it will then contain it (X or O) instead, automatically drawing it to the board the next time we render it. Now lets define the cpu_turn method:
Code:
def cpu_turn
move = cpu_find_move
@places[move] = @cpu
put_line
puts "#{@cpu_name} marks #{move.upcase}"
check_game(@user)
end
The first thing happening in this method is we call another function called cpu_find_move (we will define it later) which analyses the positions available to determine the best move. We then assign the returned value to the @places hash and output the move notifying the user what move was just placed. Finally we call a soon to be defined method called check_game which checks to see if anyone has won or if it is a stalemate. This method expects a parameter letting it know whose turn is next if the game is not over. Before we define the cpu_find_move method lets first define two functions that it relies on to analyse the board. The first function is called times_in_column which expects 2 parameters, the first being an array which is the column (of the columns in the @columns array) on the board we want to analyse and the second is what we are looking for (either X or O); and it will return how many times the item is in the column:
Code:
def times_in_column arr, item
times = 0
arr.each do |i|
times += 1 if @places[i] == item
unless @places[i] == item || @places[i] == " "
#oppisite piece is in column so column cannot be used for win.
#therefore, the strategic thing to do is choose a dif column so return 0.
return 0
end
end
times
end
You will notice we are checking if any of the slots in the column are either a space or the item we are looking for. If they are neither then the only thing it can possibly be is the other player (X or O). And since we cannot win on a column that is occupied by the other player we instantly return 0. The next function we will define is called empty_in_column this one only expects one parameter which is also a column reference and just returns the first empty slot it finds in the column:
Code:
def empty_in_column arr
arr.each do |i|
if @places[i] == " "
return i
end
end
end
Now that we have both of those defined we can now define the cpu_find_move method. The first thing we will want to do is determine if there is any move possible that will cause a win and if so, return it. You can determine this by checking to see if any of the @columns that define a win already contain 2 of cpu's moves which indicates a 3rd move into that column will be a win:
Code:
@columns.each do |column|
if times_in_column(column, @cpu) == 2
return empty_in_column column
end
end
If there is no moves available that will cause a win, the next thing to look for is if there are any moves available that will cause a loose. What I mean is, if there is a place the user can move to win. If there is, we need to move there first to block it:
Code:
@columns.each do |column|
if times_in_column(column, @user) == 2
return empty_in_column column
end
end
If neither of these find anything then there is no possible winning moves either way. So what we want to do now is build up to a winning move. To do this we check to see if any column has at least one of cpu's moves so we can add to it:
Code:
@columns.each do |column|
if times_in_column(column, @cpu) == 1
return empty_in_column column
end
end
If that didn't find anything either, then at this point we can just move to any empty slot. To make it seem more natural lets try to find a random empty slot, but if our first random slot is not empty, then at that point lets just move to the first empty slot we find:
Code:
#no strategic spot found so just find a random empty
k = @places.keys;
i = rand(k.length)
if @places[k[i]] == " "
return k[i]
else
#random selection is taken so just find the first empty slot
@places.each { |k,v| return k if v == " " }
end
That is the final part of the cpu_find_move function. So we can move on to creating the user_turn method. What we want to do here is draw the board so the user can decide where to move and then ask them to type in the slot they would like to place a move. We need to check their input to make sure it is either a valid move or the word "exit." If it is a valid move, make the move, or if it is "exit" then exit the program otherwise notify them to try again:
Code:
def user_turn
put_line
puts " RUBY TIC TAC TOE"
draw_game
puts " #{@user_name}, please make a move or type 'exit' to quit"
STDOUT.flush
input = gets.chomp.downcase
put_bar
if input.length == 2
a = input.split("")
if(['a','b','c'].include? a[0])
if(['1','2','3'].include? a[1])
if @places[input] == " "
@places[input] = @user
put_line
puts "#{@user_name} marks #{input.upcase}"
check_game(@cpu)
else
wrong_move
end
else
wrong_input
end
else
wrong_input
end
else
wrong_input unless input == 'exit'
end
end
We already know we still need to define the check_game method, we also now need to define the wrong_move and wrong_input methods. If the user input a valid slot but it is not empty then we call wrong_move but if it is not a valid slot at all then we call wrong_input. We define these methods like so:
Code:
def wrong_input
put_line
puts "Please specify a move with the format 'A1' , 'B3' , 'C2' etc."
user_turn
end
def wrong_move
put_line
puts "You must choose an empty slot"
user_turn
end
Now we can proceed to creating the check_game method. first we will want to see if the cpu or the user has won, if so, output who won and exit the game. If not, check to see if there are any moves left and call the appropriate players turn function, otherwise, output that the game is over and it is a draw. Here is the method definition:
Code:
def check_game(next_turn)
game_over = nil
@columns.each do |column|
# see if cpu has won
if times_in_column(column, @cpu) == 3
put_line
puts "Game Over -- #{@cpu_name} WINS!!!"
game_over = true
end
# see if user has won
if times_in_column(column, @user) == 3
put_line
puts "Game Over -- #{@user_name} WINS!!!"
game_over = true
end
end
unless game_over
if(moves_left > 0)
if(next_turn == @user)
user_turn
else
cpu_turn
end
else
put_line
puts "Game Over -- DRAW!"
end
end
end
There is now only one method left to define; the moves_left method that determines if it is a stalemate or not. Here it is:
Quote:def moves_left
slots = 0
@places.each do |k, v|
slots += 1 if v == " "
end
slots
end
And there you have it, you can now execute this program on the command line and it will play a game of tic tac toe with you. Like I said, I just started playing with Ruby, so if you see I did something wrong or find a way it could be improved, I hope you enjoyed the tutorial.