frc-frecon/frecon

View on GitHub
lib/frecon/position.rb

Summary

Maintainability
B
5 hrs
Test Coverage
# lib/frecon/position.rb
#
# Copyright (C) 2014 Christopher Cooper, Sam Craig, Tiger Huang, Vincent Mai, Sam Mercier, and Kristofer Rye
#
# This file is part of FReCon, an API for scouting at FRC Competitions, which is
# licensed under the MIT license.  You should have received a copy of the MIT
# license with this program.  If not, please see
# <http://opensource.org/licenses/MIT>.

require 'frecon/base/bson'
require 'frecon/base/environment'
require 'frecon/base/object'
require 'frecon/base/variables'

module FReCon
    # Public: A wrapper to handle converting team positions and storing them.
    class Position

        # Public: The alliance part of the position
        #
        # Examples
        #
        #   position = Position.new('r2')
        #   position.alliance
        #   # => :red
        attr_reader :alliance

        # Public: The slot part of the position.
        #
        # Examples
        #
        #   position = Position.new('r2')
        #   position.number
        #   # => 2
        attr_reader :number

        # Public: Convert a stored position to a Position object.
        #
        # object - String representation of a position (mongoized)
        #
        # Returns Position parsed from object.
        def self.demongoize(object)
            # `object' should *always* be a String (since MatchNumber#mongoize returns a
            # String which is what is stored in the database)
            raise ArgumentError, "`object' must be a String" unless object.is_a?(String)

            Position.new(object)
        end

        # Public: Convert a Position object to a storable string representation.
        #
        # object - A Position, String, or Hash.  If Position, run #mongoize on it.
        #          If String, create a new Position object for it, then run
        #          #mongoize on it. If Hash, convert its keys to symbols, then
        #          pull out the :alliance and :number keys to generate a Position.
        #
        # Returns String containing the mongo-ready value for the representation.
        def self.mongoize(object)
            case object
            when Position
                object.mongoize
            when String
                Position.new(object).mongoize
            when Hash
                # Convert keys to symbols if necessary.
                object = Hash[object.map { |key, value| [key.to_sym, value] }]
                Position.new(object[:alliance], object[:number]).mongoize
            else
                object
            end
        end

        # Public: Convert a Position object to a storable string representation for
        # queries.
        #
        # object - A Position, String, or Hash.  If Position, run #mongoize on it.
        #          If String, create a new Position object for it, then run
        #          #mongoize on it. If Hash, convert its keys to symbols, then
        #          pull out the :alliance and :number keys to generate a Position.
        #
        # Returns String containing the mongo-ready value for the representation.
        def self.evolve(object)
            case object
            when Position
                object.mongoize
            when String
                Position.new(object).mongoize
            when Hash
                # Convert keys to symbols if necessary.
                object = Hash[object.map { |key, value| [key.to_sym, value] }]
                Position.new(object[:alliance], object[:number]).mongoize
            else
                object
            end
        end

        # Public: Convert to a storable string representation.
        #
        # Returns String representing the Position's data.
        def mongoize
            to_s
        end

        def initialize(*args)
            if args.length == 1
                # Match `string' against the regular expression, described below.
                #
                # This regular expression matches all values for `string' where
                # the first letter is either 'r' or 'b' (case-insensitive due to /i
                # at the end of the regular expression) and the last one-or-more
                # characters in the string are digits 0-9. Anything between those two
                # that is either a letter or an underscore is not retained, but
                # if other characters exist (e.g. spaces as of right now) `string'
                # will not match.
                #
                # You can use any words you like if you have more than just
                # 'r<n>' or 'b<n>', for example 'red_2' matches just the same
                # as 'r2', or, just for fun, just the same as 'royal______2'.
                #
                # This behavior may change in the future.
                match_data = args[0].match(/^([rb])[a-z\_]*([0-9]+)/i)

                # Note: if matched at all, match_data[0] is the entire
                # string that was matched, hence the indices that start
                # at one.

                raise ArgumentError, 'string is improperly formatted' unless match_data

                @alliance = case match_data[1].downcase
                            when 'b'
                                :blue
                            when 'r'
                                :red
                            else
                                raise ArgumentError, "alliance character must be in ['b', 'r']"
                            end

                position_number = match_data[2].to_i
                raise ArgumentError, 'position number must be in [1, 2, 3]' unless [1, 2, 3].include?(position_number)

                @number = position_number
            elsif args.length == 2
                raise TypeError, 'alliance must be a Symbol or String' unless args[0].is_a?(Symbol) || args[0].is_a?(String)
                raise ArgumentError, 'alliance must be in [:blue, :red]' unless [:blue, :red].include?(args[0].to_sym)

                @alliance = args[0].to_sym

                raise TypeError, 'second argument must be an Integer' unless args[1].is_an?(Integer)
                raise ArgumentError, 'second argument must be in [1, 2, 3]' unless [1, 2, 3].include?(args[1])

                @number = args[1]
            else
                raise ArgumentError, "wrong number of arguments (#{args.length} for [1, 2])"
            end
        end

        # Public: Convert to a String.
        #
        # Returns String representing the position data.
        def to_s
            "#{@alliance[0]}#{@number}"
        end

        # Public: Determine if Position is on blue alliance.
        def is_blue?
            @alliance == :blue
        end

        # Public: Determine if Position is on red alliance.
        def is_red?
            @alliance == :red
        end

        alias_method :was_blue?, :is_blue?
        alias_method :was_red?, :is_red?

    end
end