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.