
-----------------------------------
rdrake
Fri Dec 09, 2005 11:27 pm

Tic-Tac-Toe
-----------------------------------
Kinda messy code, but it works well.  It's your basic tic-tac-toe game.  Not much variation, just works the way it's suppost to.

########################
## Tic-Tac-Toe        ##
## Dec. 9/05          ##
########################

# Declare variables
row_1  = [" ", " ", "1", " ", "|", " ", "2", " ", "|", " ", "3", " ", " "]
row_2  = [" ", " ", "4", " ", "|", " ", "5", " ", "|", " ", "6", " ", " "]
row_3  = [" ", " ", "7", " ", "|", " ", "8", " ", "|", " ", "9", " ", " "]
line   = "------------"
number = 1
piece  = "X"

# Main program loop
loop do
  # Draw the board
  row_1.each do |char|
    putc char
  end
  puts "\n#{line}"
  row_2.each do |char|
    putc char
  end
  puts "\n#{line}"
  row_3.each do |char|
    putc char
  end

  # Get player input
  puts "\nPlayer #{number}:  Where would you like to move? (1 - 9)\n"
  move = gets.chomp.to_i

  # Make sure they don't move over another piece
  if move < 4 then
    num = move * 4 - 2
    puts num
    if row_1[num] == "X" or row_1[num] == "O" then
      puts "Can't move there!"
      next;
    end
  elsif move > 3 and move < 7 then
    num = move * 4 - 14
    puts num
    if row_2[num] == "X" or row_2[num] == "O" then
      puts "Can't move there!"
      next;
    end
  elsif move > 6 and move < 10 then
    num = move * 4 - 26
    puts num
    if row_3[num] == "X" or row_3[num] == "O" then
      puts "Can't move there!"
      next;
    end
  end

  # Place piece if all is well
  if move == 1 then
    row_1[2] = piece
  elsif move == 2 then
    row_1[6] = piece
  elsif move == 3 then
    row_1[10] = piece
  elsif move == 4 then
    row_2[2] = piece
  elsif move == 5 then
    row_2[6] = piece
  elsif move == 6 then
    row_2[10] = piece
  elsif move == 7 then
    row_3[2] = piece
  elsif move == 8 then
    row_3[6] = piece
  elsif move == 9 then
    row_3[10] = piece
  end
  
  # Check for a winner
  if row_1[2] == "X" and row_1[6] == "X" and row_1[10] == "X" then
    puts "Player #{number} wins!"
    break;
  elsif row_1[2] == "O" and row_1[6] == "O" and row_1[10] == "O" then
    puts "Player #{number} wins!"
    break;
  elsif row_2[2] == "X" and row_2[6] == "X" and row_2[10] == "X" then
    puts "Player #{number} wins!"
    break;
  elsif row_2[2] == "O" and row_2[6] == "O" and row_2[10] == "O" then
    puts "Player #{number} wins!"
    break;
  elsif row_3[2] == "X" and row_3[6] == "X" and row_3[10] == "X" then
    puts "Player #{number} wins!"
    break;
  elsif row_3[2] == "O" and row_3[6] == "O" and row_3[10] == "O" then
    puts "Player #{number} wins!"
    break;
  ##########
  elsif row_1[2] == "X" and row_2[2] == "X" and row_3[2] == "X" then
    puts "Player #{number} wins!"
    break;
  elsif row_1[2] == "O" and row_2[2] == "O" and row_3[2] == "O" then
    puts "Player #{number} wins!"
    break;
  elsif row_1[6] == "X" and row_2[6] == "X" and row_3[6] == "X" then
    puts "Player #{number} wins!"
    break;
  elsif row_1[6] == "O" and row_2[6] == "O" and row_3[6] == "O" then
    puts "Player #{number} wins!"
    break;
  elsif row_1[10] == "X" and row_2[10] == "X" and row_3[10] == "X" then
    puts "Player #{number} wins!"
    break;
  elsif row_1[10] == "O" and row_2[10] == "O" and row_3[10] == "O" then
    puts "Player #{number} wins!"
    break;
  ##########
  elsif row_1[2] == "X" and row_2[6] == "X" and row_3[10] == "X" then
    puts "Player #{number} wins!"
    break;
  elsif row_1[2] == "O" and row_2[6] == "O" and row_3[10] == "O" then
    puts "Player #{number} wins!"
    break;
  elsif row_1[10] == "X" and row_2[6] == "X" and row_3[2] == "X" then
    puts "Player #{number} wins!"
    break;
  elsif row_1[10] == "O" and row_2[6] == "O" and row_3[2] == "O" then
    puts "Player #{number} wins!"
    break; 
  elsif row_1[2] == "X" or row_1[2] == "O" and row_1[6] == "X" or row_1[6] == "O" and row_1[10] == "X" or row_1[10] == "O" and row_2[2] == "X" or row_2[2] == "O" and row_2[6] == "X" or row_2[6] == "O" and row_2[10] == "X" or row_2[10] == "O" and row_3[2] == "X" or row_3[2] == "O" and row_3[6] == "X" or row_3[6] == "O" and row_3[10] == "X" or row_3[10] == "O" then
    puts "No winner."
    break;
  end
  
  # Select next player
  if number == 2 then
    number = 1
    piece = "X"
  else
    number = 2
    piece = "O"
  end
endIf there's too many lines, I can put it in a file and attach it instead.  Comments/suggestions are welcome.

-----------------------------------
wtd
Fri Dec 09, 2005 11:53 pm


-----------------------------------
Now, rewrite it with some thought to separating logic and display code.

-----------------------------------
wtd
Fri Dec 09, 2005 11:56 pm


-----------------------------------
Some simple things while you ponder that...

  # Draw the board
  puts row_1.join
  puts line
  puts row_2.join
  puts line
  puts row_3.join

Replaces:

  # Draw the board
  row_1.each do |char|
    putc char
  end
  puts "\n#{line}"
  row_2.each do |char|
    putc char
  end
  puts "\n#{line}"
  row_3.each do |char|
    putc char
  end

  # Place piece if all is well  
  if move == 1 then
    row_1[2] = piece
  elsif move == 2 then
    row_1[6] = piece
  elsif move == 3 then
    row_1[10] = piece
  elsif move == 4 then
    row_2[2] = piece
  elsif move == 5 then
    row_2[6] = piece
  elsif move == 6 then
    row_2[10] = piece
  elsif move == 7 then
    row_3[2] = piece
  elsif move == 8 then
    row_3[6] = piece
  elsif move == 9 then
    row_3[10] = piece
  end

Can be:
  
  index = 2 + (move - 1) * 4
  case move
     when 1 .. 3
        row_1[index] = piece
     when 4 .. 6
        row_2[index] = piece
     when 7 .. 9
        row_3[index] = piece
  end

-----------------------------------
wtd
Sat Dec 10, 2005 12:18 am


-----------------------------------
It's worth noting that you don't need three separate "row" variables.  You can easily have:

rows = [[" ", " ", "1", " ", "|", " ", "2", " ", "|", " ", "3", " ", " "],
        [" ", " ", "4", " ", "|", " ", "5", " ", "|", " ", "6", " ", " "],
        [" ", " ", "7", " ", "|", " ", "8", " ", "|", " ", "9", " ", " "]]

-----------------------------------
rdrake
Sat Dec 10, 2005 8:37 pm


-----------------------------------
Now, rewrite it with some thought to separating logic and display code.What exactly do you mean?  Can you provide an example.  I'm fairly new to Ruby still.

Here's a version with nicer classes instead of just random code.
########################
## Tic-Tac-Toe        ##
## Dec. 9/05          ##
########################

class TTTGame
  def initialize
    # Declare variables
    @row_1  = [" ", " ", "1", " ", "|", " ", "2", " ", "|", " ", "3", " ", " "]
    @row_2  = [" ", " ", "4", " ", "|", " ", "5", " ", "|", " ", "6", " ", " "]
    @row_3  = [" ", " ", "7", " ", "|", " ", "8", " ", "|", " ", "9", " ", " "] 
    line   = "------------"
    number = 1
    piece  = "X"
  end
  def drawBoard
    # Draw the board
    puts @row_1.join
    puts line
    puts @row_2.join
    puts line
    puts @row_3.join
  end
  def isWinner
  # Check for a winner
    if @row_1[2] == "X" and @row_1[6] == "X" and @row_1[10] == "X" then
      puts "Player #{number} wins!"
      break;
    elsif @row_1[2] == "O" and @row_1[6] == "O" and @row_1[10] == "O" then
      puts "Player #{number} wins!"
      break;
    elsif @row_2[2] == "X" and @row_2[6] == "X" and @row_2[10] == "X" then
      puts "Player #{number} wins!"
      break;
    elsif @row_2[2] == "O" and @row_2[6] == "O" and @row_2[10] == "O" then
      puts "Player #{number} wins!"
      break;
    elsif @row_3[2] == "X" and @row_3[6] == "X" and @row_3[10] == "X" then
      puts "Player #{number} wins!"
      break;
    elsif @row_3[2] == "O" and @row_3[6] == "O" and @row_3[10] == "O" then
      puts "Player #{number} wins!"
      break;
    elsif @row_1[2] == "X" and @row_2[2] == "X" and @row_3[2] == "X" then
      puts "Player #{number} wins!"
      break;
    elsif @row_1[2] == "O" and @row_2[2] == "O" and @row_3[2] == "O" then
      puts "Player #{number} wins!"
      break;
    elsif @row_1[6] == "X" and @row_2[6] == "X" and @row_3[6] == "X" then
      puts "Player #{number} wins!"
      break;
    elsif @row_1[6] == "O" and @row_2[6] == "O" and @row_3[6] == "O" then
      puts "Player #{number} wins!"
      break;
    elsif @row_1[10] == "X" and @row_2[10] == "X" and @row_3[10] == "X" then
      puts "Player #{number} wins!"
      break;
    elsif @row_1[10] == "O" and @row_2[10] == "O" and @row_3[10] == "O" then
      puts "Player #{number} wins!"
      break;
    elsif @row_1[2] == "X" and @row_2[6] == "X" and @row_3[10] == "X" then
      puts "Player #{number} wins!"
      break;
    elsif @row_1[2] == "O" and @row_2[6] == "O" and @row_3[10] == "O" then
      puts "Player #{number} wins!"
      break;
    elsif @row_1[10] == "X" and @row_2[6] == "X" and @row_3[2] == "X" then
      puts "Player #{number} wins!"
      break;
    elsif @row_1[10] == "O" and @row_2[6] == "O" and @row_3[2] == "O" then
      puts "Player #{number} wins!"
      break; 
    elsif @row_1[2] == "X" or @row_1[2] == "O" and @row_1[6] == "X" or @row_1[6] == "O" and @row_1[10] == "X" or @row_1[10] == "O" and @row_2[2] == "X" or @row_2[2] == "O" and @row_2[6] == "X" or @row_2[6] == "O" and @row_2[10] == "X" or @row_2[10] == "O" and @row_3[2] == "X" or @row_3[2] == "O" and @row_3[6] == "X" or @row_3[6] == "O" and @row_3[10] == "X" or @row_3[10] == "O" then
      puts "No winner."
      break;
    end
  end
  def placePiece
    # Get player input
    puts "\nPlayer #{@number}:  Where would you like to move? (1 - 9)\n"
    move = gets.chomp.to_i
    # Make sure they don't move over another piece
    if move < 4 then
      num = move * 4 - 2
      if @row_1[num] == "X" or @row_1[num] == "O" then
        puts "Can't move there!"
        next;
      end
    elsif move > 3 and move < 7 then
      num = move * 4 - 14
      if @row_2[num] == "X" or @row_2[num] == "O" then
        puts "Can't move there!"
        next;
      end
    elsif move > 6 and move < 10 then
      num = move * 4 - 26
      if @row_3[num] == "X" or @row_3[num] == "O" then
        puts "Can't move there!"
        next;
      end
    end
    # Place piece
    @index = @move * 4 - 2
    case @move
      when 1 .. 3
          @row_1[index] = piece
      when 4 .. 6
          @index -= 12
          @row_2[index] = piece
      when 7 .. 9
          @index -= 24
          @row_3[index] = piece
    end
  end
  def changeTurn
    # Select next player
    if @number == 2 then
      @number = 1
      @piece = "X"
    else
      @number = 2
      @piece = "O"
    end
  end
end
# Main program loop
loop do
  game = TTTGame.new
  game.drawBoard
  game.placePiece
  game.isWinner
  game.changeTurn
end

-----------------------------------
wtd
Sat Dec 10, 2005 8:44 pm


-----------------------------------
Now, rewrite it with some thought to separating logic and display code.What exactly do you mean?  Can you provide an example.  I'm fairly new to Ruby still.

Thinking about tic-tac-toe, we have a 3x3 grid.  We can easily represent that in Ruby as an array of three arrays.  Elements in the array can have one of three values.  The possibilities are X, O and Empty.  They should all start off empty.

X = 0
O = 1
EMPTY = nil

board = Array.new(3) { Array.new(3) { EMPTY } }

Now, we'd want to print this.  So, let's define a new method to do this.  Except we'll make it a bit more general and just have this generate a string representation of the board.

def board_to_s(board)
   board.zip([0, 1, 2]).collect do |row, row_number|
      row.zip([0, 1, 2]).collect do |square, square_number| 
         case square 
            when X then "X" 
            when O then "O"
            else row_number * 3 + square_number + 1             
         end
      end.join("|")
   end.join("-+-+-")
end

Now when we want to print a board, we can write:

puts board_to_s(board)

But that's pretty redundant, so let's turn Board into a class.  The initialize method will do the setup for us.  The to_s method will do the same as our board_to_s method.

class Board
   X = 0
   O = 1
   EMPTY = nil
   
   def initialize
      @board = Array.new(3) { Array.new(3) { EMPTY } }
   end
   
   def to_s
      @board.zip([0, 1, 2]).collect do |row, row_number|
         row.zip([0, 1, 2]).collect do |square, square_number| 
            case square 
               when X then "X" 
               when O then "O"
               else row_number * 3 + square_number + 1             
            end
         end.join("|")
      end.join("-+-+-")
   end
end

And now we can:

puts Board.new

And get:

1|2|3
-+-+-
4|5|6
-+-+-
7|8|9

This is possible because the puts method automatically calls the to_s method on an object, and the new method of the Board class creates a new Board object.
