5
\$\begingroup\$

I am building my first OOP program, Tic Tac Toe. I want to conquer it in chunks, but the first part for me is the intialization of the program and the creation of the two players. I want to get some feedback about how I structured this before I move on to build the board and the actual gameplay logic.

For instance, should I have made the player_names assignment method part of the Player class? Or is the parent TicTacToe class good?

class TicTacToe

    def initialize
        @player1 = Player.new
        @player2 = Player.new
        game_sequence
    end

    def game_sequence
        puts "Let us play a nice game of Tic Tac Toe!"
        player_names

    end

    def player_names
        puts "Player 1, what is your name?"
        @player1.name = gets.chomp
        puts "Thanks, #{@player1.name}!  Would you like to be X or O?"

        answer = ""

        until ["X","O"].include?(answer)
            answer = gets.chomp
            if ["X","O"].include?(answer)
                @player1.mark = answer
                break
            end
            puts "You must enter in either X or O"
        end

        case @player1.mark
        when "X"
            @player2.mark = "O"
        when "O"
            @player2.mark = "X"
        end

        puts "#{@player1.name}\'s mark is #{@player1.mark} and Player 2\'s mark is #{@player2.mark}"
        puts "Player 2, what is your name?"
        @player2.name = gets.chomp

    end

    class Player

        attr_accessor :name, :mark

        def initialize(name = nil, mark = nil)
            @name = name
            @mark = mark
        end

    end

end

TicTacToe.new
\$\endgroup\$
1
\$\begingroup\$

When coming up with an OO design, always ask yourself "What is the responsibility of this class?" There should be only one. If you have to describe it with a bunch of ANDs, e.g. "it runs the game and prompts for player names and sets up the board and..." then it is a sign you may want to break it down further. This is called the Single Responsibility Principle. Resist the temptation to put all functionality into one class that does everything, as it is very easy to grow the class into the antipattern known as the God Object. Sometimes naming helps. If instead of TicTacToe, you called the object GameRunner (for example), you may be less tempted to put any and all Tic Tac Toe related things into it.

In your case, I'd try to keep TicTacToe as lean as possible. Prompting for a player's name should be something a Player object knows how to do, so I'd move that to the Player class. Also prompting for a mark can certainly be moved into the Player class. Possible interface:

marks = ["X", "O"] #this would be in the initialization
player1.mark = player1.select_mark(marks)
player2.mark = marks - player1.mark

Also, you'll want to create an object to represent the game board.

\$\endgroup\$
  • 1
    \$\begingroup\$ To add to this answer, I would also put all of your game related classed into their own module, for instance TicTacToe::GameRunner, TicTacToe::Player, TicTacToe::GameBoard, etc. \$\endgroup\$ – Greg Burghardt Apr 21 '15 at 16:00
1
\$\begingroup\$

To expand on what @MarkThomas wrote in his answer, you really have 6 main concerns in your game that should be decoupled:

  1. The game itself
  2. Players
  3. The game board
  4. Game board squares
  5. The view
  6. The thing that knows you're in a console

This leads me to believe there should be 6 classes, which should be contained within their own module/namespace to better organize the code:

TicTacToe::Game

module TicTacToe
  class Game
    def initialize(player1, player2, view)
      @player1 = player1
      @player2 = player2
      @view = view
      @board = Board.new
      @view.board = @board
    end

    def run
      # inside a loop, prompt players to do stuff, then call @view.render
    end
  end
end

This class doesn't have to know it's in the console. It just needs to know who the two players are, and what the "view" is that should be rendered on each iteration of the main game loop.

TicTacToe::ConsoleBoardView

module TicTacToe
  class ConsoleBoardView
    attr_accessor :board

    def render
      # a bunch of puts calls to render the @board
    end
  end
end

This knows that the game is running in the console, and renders the @board using a bunch of calls to puts. The view is decoupled from the game, which now could also be run as a web app.

From the standpoint of the outside world, you need one more class to glue things together and start the game.

TicTacToe::ConsoleGameRunner

module TicTacToe
  class ConsoleGameRunner
    def self.run
      puts "Player 1 name: "
      player1 = Player.new gets.chomp
      puts "Player 2 name: "
      player2 = Player.new gets.chomp

      game = Game.new player1, player2, ConsoleBoardView.new
      game.run
    end
  end
end

The ConsoleGameRunner knows how to bring the game to life and run it from the command line.

Lastly, you need the business classes of your game:

module TicTacToe
  class Player
    attr_accessor :name

    def initialize(name)
      @name = name
    end
  end
end

module TicTacToe
  class Board
    def initialize
      @squares = [
        [BoardSquare.new, BoardSquare.new, BoardSquare.new],
        [BoardSquare.new, BoardSquare.new, BoardSquare.new],
        [BoardSquare.new, BoardSquare.new, BoardSquare.new]
      ]
    end
  end
end

module TicTacToe
  class BoardSquare
    attr_accessor :marked

    def marked?
      @marked
    end
  end
end

These classes would have the methods that implement the "business of playing tic tac toe". The Game and ConsoleBoardView delegate most behavior to these classes for the game logic. The Game just reacts to what these other classes do, and the ConsoleBoardView just spits out the current state of the board.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.