class Board #Constants EMPTY_POS = ' ' COMPUTER_PLAYER = 'x' HUMAN_PLAYER = 'o' SIZE = 6 #Initialize the data required for a TicTacToe board def initialize @current_player = 'x' @board = Array.new(SIZE) { Array.new(SIZE) { EMPTY_POS } } end # Scan the board and if we see any open positions, we are done def board_full @board.each do |row| row.each do |position| if position == EMPTY_POS return false end end end # Scanned entire board, so there are no more open positions return true end # Scan the board and if we see any filled positions, we are done def board_empty @board.each do |row| row.each do |position| if position != EMPTY_POS return false end end end # Scanned entire board, so there are no more filled positions return true end # Determines and returns the winner of the game of tic-tac-toe. # Checks rows, columns, and diagonals for the winner. def winner winner = winner_rows if winner return winner end winner = winner_cols if winner return winner end winner = winner_diagonals if winner return winner end # No winners return end #Determine if there is a winner in the rows. #If all the pieces in the row are the same as the first, there is a winner def winner_rows for row_index in 0..board_max_index piece_type = @board[row_index][0] for col_index in 1..board_max_index if piece_type != @board[row_index][col_index] break elsif col_index == board_max_index and piece_type != EMPTY_POS return piece_type end end end return end #Determine if there is a winner in the columns #If all the pieces in a column are the same as the first, there is a winner def winner_cols for col_index in 0..board_max_index piece_type = @board[0][col_index] for row_index in 1..board_max_index if piece_type != @board[row_index][col_index] break elsif row_index == board_max_index and piece_type != EMPTY_POS return piece_type end end end return end #Determine if there is a winner in the diagonals #If all the pieces in a diagonal are the same as the first, there is a winner def winner_diagonals piece_type = @board[0][0] #Check the diagonal from (0,0) to (2,2) for index in 1..board_max_index if piece_type != @board[index][index] break elsif index == board_max_index and piece_type != EMPTY_POS return piece_type end end #Check the diagonal from (2,0) to (0,2) piece_type = @board[0][board_max_index] row_index = 0 col_index = board_max_index while row_index < board_max_index row_index += 1 col_index -= 1 if piece_type != @board[row_index][col_index] break elsif row_index == board_max_index and piece_type != EMPTY_POS return piece_type end end return end # Print the board array and for all blank positions, def display puts "Starting display...." puts '+-----------------------------+' for row in 0..board_max_index # print has to be used when we don't want to output a # line break print '| ' for col in 0..board_max_index s = @board[row][col] if s == EMPTY_POS print col + (row * SIZE) + 1 else print " #{s}" end if ((col + (row * SIZE) + 1) < 10) print ' | ' else print ' | ' end end puts puts '+-----------------------------+' end end def ask_player_for_move(current_player) if current_player == COMPUTER_PLAYER computer_move() else human_move() end end # The logic used by the computer when it picks its turn. # The computer always picks the middle square first. # The computer opts to play offsensively instead of defensively. # First it searches for positions that may cause it to win. # If it fails to find such a position, it searches for positions that would # cause the human to win, and will block said position if found. # If it doesnt find a spot based on those criterion it picks randomly. def computer_move row = -1 col = -1 # If the middle slot is empty, take it. if @board[3][3] == EMPTY_POS row = 3 col = 3 @board[row][col] = @current_player else #found keeps track of whether or not any of the computers attempts to find #a good spot to take are successful. "F" means false, "T" means true #I used an array because it was the only way I could force pass-by-reference (that I know of) found = "F" #Look for columns, rows, and diagonals where the computer may win check_cols(COMPUTER_PLAYER, found) check_rows(COMPUTER_PLAYER,found) check_diagonals(found) #Look for columns and rows where the human may win #Note that the human can never win on diagonals because the computer always takes the middle #position first, so it is a waste of time searching for that winner. check_cols(HUMAN_PLAYER, found) check_rows(HUMAN_PLAYER, found) #If after all this, we find nothing worth while to do, just randomly pick. if found == "F" until validate_position(row, col, false) row = rand(@board.size) col = rand(@board.size) end @board[row][col] = @current_player end end end #Check each of the rows where two spaces are of the #same type and one is unfilled, if this is found take #the spot. #player - the type of piece we are searching for #found - whether or not a good move was found def check_rows(player, found) find_best_spot(1,0,0,1,player, found) find_best_spot(0,0,0,1,player, found) find_best_spot(2,0,0,1,player, found) find_best_spot(3,0,0,1,player, found) find_best_spot(4,0,0,1,player, found) find_best_spot(5,0,0,1,player, found) end #Check each of the columns where two spaces are of the #same type and one is unfilled, if this is found take #the spot. #player - the type of piece we are searching for #found - whether or not a good move was found def check_cols(player, found) find_best_spot(0,1,1,0,player, found) find_best_spot(0,1,0,0,player, found) find_best_spot(0,1,2,0,player, found) find_best_spot(0,1,3,0,player, found) find_best_spot(0,1,4,0,player, found) find_best_spot(0,1,5,0,player, found) end #Check each of the diagonals where two spaces are of the #same type and one is unfilled, if this is found take #the spot. #player - the type of piece we are searching for #found - whether or not a good move was found def check_diagonals(found) find_best_spot(0,1,0,1,COMPUTER_PLAYER, found) find_best_spot(2,-1,0,1,COMPUTER_PLAYER, found) end # Determines how to find the best spot. # If there are two pieces of the same kind in row, column, or diagonal and one spot # unfilled then take the unfilled spot. # row_start - the starting board row index # row_inc - the amount to increment the row index by # col_start - the starting board column index # col_end - the amount to increment the col index by # piece - the type of piece we are looking for def find_best_spot(row_start, row_inc, col_start, col_inc, piece, found) if found == "F" row_current = row_start col_current = col_start counter = 0 row = -1 col = -1 i = 0 #Traverse the board and increment the counter when the specified piece is found. while(i < SIZE) if( @board[row_current][col_current] == piece ) counter = counter + 1 elsif( @board[row_current][col_current] == EMPTY_POS ) row = row_current col = col_current end row_current = row_current + row_inc col_current = col_current + col_inc i = i + 1 end #If two of the specified piece are found, fill the third spot. if( counter > 5 and( row != -1 or col != -1 ) ) @board[row][col] = @current_player found[0] = 'T' end end end #Prompts the user to take their turn. #If the position is valid, they can take their turn. #If the position is not valid, reprompt for a valid position. def human_move played = false while not played puts @current_player + " Where would you like to play?" move = gets.to_i - 1 col = move % @board.size row = (move - col) / @board.size puts "You have chosen: Row ==> " + row.to_s + " Col ==> " + col.to_s if validate_position(row, col) @board[row][col] = @current_player played = true end end end #Determine if a position is valid. #If a position is not occupied and is in the range 0 <= x <= 2, 0 <= y <= 2 #then it is valid. def validate_position(row, col, output=true) if 0 <= row and row <= @board.size and 0 <= col and col <= @board.size if @board[row][col] == EMPTY_POS return true else if output puts "That position is occupied." end end else if output puts "Invalid position." end end return false end #Determine the next turn based upon the current turn. def get_next_turn if @current_player == 'x' @current_player = 'o' else @current_player = 'x' end return @current_player end #The highest index of the board. def board_max_index return @board.size - 1 end end