You may work on this lab with another student if you wish. If you do, put a comment in the program code that includes both of your names, and both of you should submit the code.
Deliverables:
In this lab, you will develop a program that allows two players to play Tic Tac Toe. In Tic Tac Toe, two players alternate placing their marks ("X"'s and "O"'s, respectively) in the of a 9 positions of a 3x3 grid. The first player to put three of their marks in a vertical, horizontal, or diagonal line is the winner. If nine marks have been placed without either player getting three marks in a row, the game ends in a tie.
In order to represent the state of play in a game of Tic Tac Toe, we will use a two-dimensional 3x3 array, where each element is nil (if the corresponding position is unoccupied), 0 if the position is occupied by the mark ("X") of the first player ("Player 0"), or 1 if the position is occupied by the mark ("O") of the second player ("Player 1"). For example, the Tic Tac Toe grid shown at right could be represented by the 2d Ruby array:
[[1, nil, 0], [nil, 0, nil], [nil, nil, nil]]
Usage:
>> grid = new_grid() => [[nil, nil, nil], [nil, nil, nil], [nil, nil, nil]] >> add_mark(grid,1,2,0) => true >> grid => [[nil, nil, nil], [nil, nil, 0], [nil, nil, nil]] >> add_mark(grid,1,2,1) => false >> grid => [[nil, nil, nil], [nil, nil, 0], [nil, nil, nil]] >> add_mark(grid,2,2,1) => true >> grid => [[nil, nil, nil], [nil, nil, 0], [nil, nil, 1]]
The RubyLabs Canvas provides three types of graphical objects (Rectangle, Line, and Circle) that are useful for the parts of a Tic Tac Toe game:
Canvas.init(90,90,"Lab13") Canvas::Rectangle.new(0, 0, 90, 50, :fill => :gray, :width => 0) Canvas::Line.new(5, 45, 85, 5, :width => 3) Canvas::Circle.new(75, 35, 10, :outline => :black, :fill => :gray, :width => 2)
In irb, create a 90x90 pixel canvas, and try to draw the Tic Tac Toe grid shown at right using rectangles, lines, and circles. Create a text file drawing.txt that contains the irb commands you used to draw this image (hint: cut & paste them from your terminal window).
Define a Ruby function display_grid(grid) that draws a game state to the Canvas. The following algorithm may be used for display_grid:
The following usage should result in the image shown above:
>> Canvas.init(90,90,"Lab13") => true >> display_grid([[1,nil,0],[nil,0,nil],[nil,nil,nil]]) => nil
With your CA, trace through the execution of the functions play and input_num, given below, for the first few moves of a game as shown here:
>> play() Player 0: Which row (0-2)? 1 Player 0: Which column (0-2)? 1 Player 1: Which row (0-2)? f input must be a number between 0 and 2 (inclusive) Player 1: Which row (0-2)? 3 input must be a number between 0 and 2 (inclusive) Player 1: Which row (0-2)? 1 Player 1: Which column (0-2)? 1 Grid position (row=1, column=1) is already occupied. Players 1: Which row (0-2)? 0 Player 1: Which column (0-2)? 0 Player 0: Which row (0-2)? 0 Player 0: Which column (0-2)? 1
def play()
# draw the inital (empty) game grid
Canvas.init(90,90,"TicTacToe")
grid = new_grid()
display_grid(grid)
player = 0
9.times {
# keep on asking for rows/columns until the user inputs a valid move
# add a mark to the correct cell
repeat = true
while repeat == true do
row = input_num("Player " + player.to_s + ": Which row (0-2)? ")
col = input_num("Player " + player.to_s + ": Which column (0-2)? ")
if add_mark(grid, row, col, player) then
repeat = false
else
puts "Grid position (row=" + row.to_s + ", column=" + col.to_s +
") is already occupied."
end
end
# redisplay grid with new mark
display_grid(grid)
if check_win(grid) then
puts "Player " + player.to_s + " won!"
return
end
# alternate between player 0 and player 1
player = (player + 1) % 2
}
# If no-one wins in 9 moves, the game is a tie.
puts "The game ended in a tie."
end
# get a number between 0 and 2, inclusive
def input_num(prompt)
num = nil
while num == nil do
input = Readline.readline(prompt)
# Determine whether input is a number by checking if it matches
# a regular expression. The ^ says that the regular expression
# has to be at the beginning of the input. The rest of the regular
# expression checks to see if the first character is either the
# numeral 0, 1, or 2
is_number = input.match("^(0|1|2)")
num = input.to_i
if is_number && num >= 0 && num <= 2 then
return num
else
puts "input must be a number between 0 and 2 (inclusive)"
num = nil
end
end
return num
end
At this point, if you supply a "stub" definition of check_win, then you should be able to play Tic Tac Toe, except that the computer will not stop when a player wins.
>> def check_win(grid) >> return false >> end => nil >> play() Player 0: Which row (0-2)? 0 Player 0: Which column (0-2)? 0 Player 1: Which row (0-2)? 0 Player 1: Which column (0-2)? 2 Player 0: Which row (0-2)? 2 Player 0: Which column (0-2)? 2 Player 1: Which row (0-2)? 1 Player 1: Which column (0-2)? 1 Player 0: Which row (0-2)? 2 Player 0: Which column (0-2)? 0 Player 1: Which row (0-2)? 1 Player 1: Which column (0-2)? 0 Player 0: Which row (0-2)? 2 Player 0: Which column (0-2)? 1 Player 1: Which row (0-2)? 1 Player 1: Which column (0-2)? 2 Player 0: Which row (0-2)? 0 Player 0: Which column (0-2)? 1 The game ended in a tie. => nil
For each row, return true if the three positions on that row are occupied by marks belonging to the same player.
For each column, return true if the three positions on that column are occupied by marks belonging to the same player.
Return true if the three positions on the diagonal line form the upper-left corner to the lower-right corner are all occupied by marks belonging to the same player.
Return true if the three positions on the diagonal line form the upper-right corner to the lower-left corner are all occupied by marks belonging to the same player.
Return false.
A helper function check_win_horiz(grid, row) that returns true if row row in grid contains three marks belonging to the same player, can be defined by implementing the following algorithm:
The other helper functions can be defined similarly.
Define the following Ruby functions:
A function check_win_horiz(grid, row) that returns true if row row in grid contains three marks belonging to the same player.
A function check_win_vert(grid, col) that returns true if column col in grid contains three marks belonging to the same player.
A function check_win_diagonal1(grid) that returns true if the diagonal from the upper-left corner to lower-right corner contains three marks belonging to the same player.
A function check_win_diagonal2(grid) that returns true if the diagonal from the upper-right corner to lower-left corner contains three marks belonging to the same player.
A function check_win(grid) that returns true if there are three marks by the same player in a horizontal, vertical, or diagonal line.
Call the play() function in irb to play your Tic Tac Toe game. Try to come up with games in which:
Copy and paste your the irb commands and outputs for these games into the file games.txt.