diff --git a/tictactoe/controller.rb b/tictactoe/controller.rb new file mode 100644 index 0000000..95d0ee3 --- /dev/null +++ b/tictactoe/controller.rb @@ -0,0 +1,77 @@ +require_relative 'globals' + +class GameController + include GameView + include GameState + include GameModel + + def run! + # Print::display("Mikes page!" , ["Some stuff is crazy", "and that is why I love it", "be peaceful my friend"], "~Kenworthy", 80) + @currState = InitState.new + Print.initialize('Hello world!', '~Kenworthy inc', 80) + + until @currState.is_a? EndState + + Print.display(@currState.getBody) + + nextState = @currState.send(Print.getInput(@currState.getQuestion)) + + (nextState = Complete.new if checkFTW(Model.board) || Model.board.full?) if @currState.is_a? Turn + + @currState = nextState + end + end + + def checkFTW(board) + puts 'Heres the board!' + p board.data + + winBoard = Array.new(3) { Array.new(board.size) { nil } } + (board.size - 1).times do |i| + winBoard[0][i] = board.data[i][0] + winBoard[1][i] = board.data[0][i] + end + winBoard[2][0] = board.data[0][0] + winBoard[2][1] = board.data[0][board.size - 1] + + # puts "Heres the unchecked winners board!" + # p winBoard + + board.size.times do |r| + board.size.times do |c| + # puts "Row #{r} Col #{c}" + # puts "Winboard value #{winBoard[1][c]} Board value #{board.data[r][c]}" + winBoard[1][c] = false unless board.data[r][c] && winBoard[1][c] && board.data[r][c].class == winBoard[1][c].class + winBoard[0][r] = false unless board.data[r][c] && winBoard[0][r] && board.data[r][c].class == winBoard[0][r].class + + if c == r + winBoard[2][0] = false unless board.data[r][c] && winBoard[2][0] && board.data[r][c].class == winBoard[2][0].class + end + if (c + r) == (board.size - 1) + winBoard[2][1] = false unless board.data[r][c] && winBoard[2][1] && board.data[r][c].class == winBoard[2][1].class + end + end + end + + # puts "here are the winners!" + puts 'There was a winner?' + p winBoard.flatten.any? + p winBoard + rcd = -1 + num = -1 + if winBoard.flatten.any? + winBoard.size.times do |c| + winBoard[c].size.times do |r| + if winBoard[r][c] + Model.winning_position = [r, c] + Model.winner = winBoard[r][c].owner + return true + end + end + end + end + false + end +end + +GameController.new.run! diff --git a/tictactoe/exceptions.rb b/tictactoe/exceptions.rb new file mode 100644 index 0000000..f5d64b7 --- /dev/null +++ b/tictactoe/exceptions.rb @@ -0,0 +1,6 @@ +class TooMuchContent < Exception +end +class NotInitialized < Exception +end +class NotImplemented < Exception +end diff --git a/tictactoe/globals.rb b/tictactoe/globals.rb new file mode 100644 index 0000000..af21489 --- /dev/null +++ b/tictactoe/globals.rb @@ -0,0 +1,4 @@ +require_relative 'view' +require_relative 'model' +require_relative 'states' +require_relative 'exceptions' diff --git a/tictactoe/model.rb b/tictactoe/model.rb new file mode 100644 index 0000000..665ed1a --- /dev/null +++ b/tictactoe/model.rb @@ -0,0 +1,92 @@ +require_relative 'globals' + +module GameModel + module Model + class << self + attr_reader :board + attr_accessor :winner, :winning_position + @initilized = false + + def initialize + @players = [nil, nil] + @board = Board.new + @initilized = true + @winner = nil + end + + def set_player(name, p) + @players[p] = Player.new(name, p == 0 ? X.new(self) : O.new(self)) + end + + def get_player(i) + @players[i] ? @players[i] : 'not set' + end + + def getBoardAsArray + string = [" \e[4m [0] [1] [2] \e[0m "] + @board.size.times do |r| + row = "[#{r}] |" + @board.size.times do |c| + row += "\e[4m " + (@board.data[r][c] ? @board.data[r][c].to_s : ' ') + " \e[0m|" + end + row += ' ' + string.push(row) + end + puts string + string + end + + def place_piece(piece, r, c) + @board.data[r][c] = piece + end + + def get_space(r, c) + @board.data[r][c] + end + end + end + + class Player + attr_reader :piece + def initialize(name, piece) + @name = name + @piece = piece + end + + def to_s + @name + '(' + @piece.to_s + ')' + end + end + + class Piece + attr_reader :owner + def initialize(owner) + @owner = owner + end + end + + class X < Piece + def to_s + 'X' + end + end + + class O < Piece + def to_s + 'O' + end + end + + class Board + attr_accessor :data, :size + def initialize(size = 3) + @size = size + # @data = [[X.new(P1.new("Mike")),nil,nil],[X.new(P1.new("Mike")),X.new(P1.new("Mike")),nil],[X.new(P1.new("Mike")),nil,X.new(P1.new("Mike"))]]#Array.new(size) { Array.new(size) { nil }}#X.new(P2.new("Mike")) } } + @data = Array.new(size) { Array.new(size) { nil } } + end + + def full? + @data.flatten.reduce(true) { |m, i| m && i } + end + end +end diff --git a/tictactoe/states.rb b/tictactoe/states.rb new file mode 100644 index 0000000..e67f506 --- /dev/null +++ b/tictactoe/states.rb @@ -0,0 +1,221 @@ +require_relative 'globals' + +class String + def is_integer? + to_i.to_s == self + end +end + +module GameState + class State + include GameModel + attr_writer :oldQuestion, :errorMessage + [:getBody, :send, :getQuestion].each do |i| + define_method(i) do |*_args| + fail NotImplemented, 'Please implement this function' + end + end + def invalidInput(error = nil) + state = self.class.new + error ? state.errorMessage = [error] : state.errorMessage = ['Invalid input!'] + state.oldQuestion = state.getQuestion + def state.getQuestion + @errorMessage + @oldQuestion + end + state + end + + def initialize + @oldQuestion = [] + end + + def commands(input) + case input.downcase + when ':newgame' || ':n' + Model.initialize + SetP1State.new + when ':quit' || ':q' + EndState.new + when ':back' || ':b' + Back.new + else + false + end + end + end + + class InitState < State + def getBody + # ["Some stuff is crazy", "and that is why I love it", "be peaceful my friend"] + ['Welcome to Tic-Tac-Toe!', + 'Available commands', + ':newgame => Start a new game', + ':quit => Quit game'] + end + + def send(input) + returned_value = commands(input) + return returned_value if returned_value + + if input == '' + Model.initialize + return SetP1State.new + end + + invalidInput + end + + def getQuestion + ['Please press enter to begin!'] + end + end + + class SetP1State < State + def getBody + ["Player 1 is currently: #{Model.get_player(0)}", + 'Commands:', ':quit => Quit the application', ':back => Go to the previous menu'] + end + + def send(input) + returned_value = commands(input) + return returned_value if returned_value + # set the name of the player and move to P2 selection state + Model.set_player(input, 0) + SetP2State.new + # If game is playing, return to game + end + + def getQuestion + ['Please enter a name for player 1 and press enter!'] + end + end + + class SetP2State < State + def getBody + ["Player 2 is currently: #{Model.get_player(1)}", + 'Commands:', ':quit => Quit the application', ':back => Go to the previous menu'] + end + + def send(input) + returned_value = commands(input) + return returned_value if returned_value + # set the name of the player and move to the next state + Model.set_player(input, 1) + + # If game is playing, return to game + + P1Turn.new + end + + def getQuestion + ['Please enter a name for player 2'] + end + end + + class Turn < State + def send(input, player) + returned_value = commands(input) + return returned_value if returned_value + + coords = input.split('') + + return invalidInput('Please enter two numbers only') if coords.size != 2 + + if coords[0].is_integer? && coords[1].is_integer? + coords.map!(&:to_i) + else + return invalidInput('Please only use numbers') + end + p coords + p Model.get_space(coords[0], coords[1]) + # binding.pry + if Model.get_space(coords[0], coords[1]) + # binding.pry + return invalidInput("There's already a piece on #{Model.get_space(coords[0], coords[1])}") + else + Model.place_piece(player.piece.class.new(player), coords[0].to_i, coords[1].to_i) + false + end + end + end + + class P1Turn < Turn + def getBody + # Get board as text + ['Player 1 turn!'] + Model.getBoardAsArray + end + + def send(input) + player = Model.get_player(0) + + returned_value = super(input, player) + return returned_value if returned_value + + P2Turn.new + end + + def getQuestion + ["Place your #{Model.get_player(0).piece}! Ex: 00 => upper left"] + end + end + + class P2Turn < Turn + def getBody + ['Player 2 turn!'] + Model.getBoardAsArray + end + + def send(input) + player = Model.get_player(1) + + returned_value = super(input, player) + return returned_value if returned_value + + P1Turn.new + end + + def getQuestion + ["Place your #{Model.get_player(1).piece}! Ex: 00 => upper left"] + end + end + + class Complete < State + def getBody + # ["Some stuff is crazy", "and that is why I love it", "be peaceful my friend"] + winning_position = Model.winning_position + p winning_position + rcd = '' + + if Model.winner + case winning_position[0] + when 0 + rcd = 'Row' + when 1 + rcd = 'Col' + when 2 + rcd = 'diag' + end + ret = ["Congratulations player #{Model.winner}", "You won on #{rcd}#{winning_position[1]}", ''] + else + ret = ['No winner!', ''] + end + + ret += Model.getBoardAsArray + end + + def send(input) + returned_value = commands(input) + return returned_value if returned_value + invalidInput + end + + def getQuestion + [''] + end + end + + class EndState < State + end + + class Back < State + end +end diff --git a/tictactoe/view.rb b/tictactoe/view.rb new file mode 100644 index 0000000..7c4956c --- /dev/null +++ b/tictactoe/view.rb @@ -0,0 +1,75 @@ +require_relative 'globals' + +module GameView + module Print + class << self + @@initilized = false + + def initialize(header, footer, screen_size, + header_alignment = :LEFT, body_alignment = :CENTER, footer_alignment = :RIGHT) + @@initilized = true + @header = header + @footer = footer + @header_alignment = header_alignment + @body_alignment = body_alignment + @footer_alignment = footer_alignment + @screen_size = screen_size + @screen_size -= 1 if (@screen_size % 2 != 0) + end + + def getInput(question) + question.each { |line| print_line(line, :LEFT) } + print '>> ' + gets.chomp + end + + def display(body) + system('clear') || system('cls') + fail NotInitialized, 'Please run the initialization before attempting display' unless @@initilized + checkContent(@header, body, @footer) + print_bar + print_line(@header, @header_alignment) + print_bar + # print_blank() + body.each { |line| print_line(line, @body_alignment) } + print_blank + print_bar + end + + def print_line(line, align) + # p line.scan(/\e/).count + padding = (@screen_size - 6 + (line.scan(/\e/).count * 4) - line.length) + if align == :LEFT + puts '** ' + line + (' ' * padding) + ' **' + elsif align == :RIGHT + puts '** ' + (' ' * (@screen_size - 6 - line.length)) + line + ' **' + elsif align == :CENTER + right_pad = padding + 1 if padding / 2 != 0 + puts '** ' + (' ' * (padding / 2)) + line + (' ' * (right_pad / 2)) + ' **' + end + end + + def print_blank + puts '** ' + (' ' * (@screen_size - 6)) + ' **' + end + + def print_bar + line = '' + nStars = ((@screen_size - 4) / 2) + nStars.times { line += '*' } + line = line + ' || ' + nStars.times { line += '*' } + puts line + end + + def checkContent(header, body, footer) + content = [header] + body + [footer] + content.max do |a, b| + b.length <=> a.length + end + fail TooMuchContent 'Reduce line size...' unless content.first.length < (@screen_size - 6) + # max(content.first.length,10) + end + end + end +end