class LZRTag::Handler::Game

Game handler class, managing game registration and game ticks.
This class manages the lifecycle of any lasertag game started in it.
This includes sending a gameTick, managing phase and game state transitions,
sending out but also receiving information on the game state via MQTT, etc.

@example

handler = LZRTag::Handler::Game.new(mqtt);

handler.register_game("My Game", SomeGame);
handler.register_game("Other game!", SomeOtherGame);

handler.start_game(SomeGame); # This will fetch the registered name and publish to MQTT
                                                                                # Alternatively, sending to Lasertag/Game/Controls/SetGame with
                                                                                # the string name will switch to the game.

# Game and phase switching is possible at any time, and will be handled asynchronously
sleep 5;
handler.set_phase(:somePhase);

Attributes

currentGame[R]

Returns the instance of the currently active game, or nil if none is present

gamePhase[R]

currently active game phase When set it will start a phase change, sending :gamePhaseEnds and :gamePhaseStarts events @see set_phase @return Symbol

gamePlayers[R]

List of in-game players. This list is mainly to keep track of which players the game is acting upon, but updating it will also send a list of player ID Strings to MQTT (Lasertag/Game/ParticipatingPlayers), and will send :playerEnteredGame and :playerLeftGame events @return Array<LZRTag::Player::Base>

Public Class Methods

new(*data, **argHash) click to toggle source
Calls superclass method LZRTag::Handler::Count::new
# File lib/lzrtag/handler/game_handler.rb, line 45
def initialize(*data, **argHash)
        super(*data, **argHash)

        @lastTick = Time.now();

        @lastGame    = nil;
        @currentGame = nil;
        @nextGame    = nil;

        @gamePhase = :idle;

        @gamePlayers = Array.new();

        @knownGames = Hash.new();

        _start_game_thread();

        @mqtt.subscribe_to "Lasertag/Game/Controls/+" do |data, topics|
                case topics[0]
                when "SetPhase"
                        phase = data.to_sym;
                        if(get_allowed_phases().include? phase)
                                set_phase(phase);
                        end
                when "SetGame"
                        if(@knownGames[data])
                                start_game(@knownGames[data])
                        elsif(data == "STOP")
                                stop_game();
                        end
                end
        end

        clean_game_topics();
        at_exit {
                clean_game_topics();
        }
end

Public Instance Methods

consume_event(event, data) click to toggle source

@private

Calls superclass method LZRTag::Handler::Count#consume_event
# File lib/lzrtag/handler/game_handler.rb, line 139
def consume_event(event, data)
        super(event, data)

        return unless @currentGame
        @currentGame.consume_event(event, data);
end
each_participating() { |pl| ... } click to toggle source

Yield for each currently in-game player

# File lib/lzrtag/handler/game_handler.rb, line 254
def each_participating()
        @gamePlayers.each do |pl|
                yield(pl)
        end
end
gamePhase=(nextPhase) click to toggle source

Alias for set_phase @see set_phase

# File lib/lzrtag/handler/game_handler.rb, line 224
def gamePhase=(nextPhase)
        set_phase(nextPhase)
end
gamePlayers=(newPlayers) click to toggle source

Update the list of in-game players. @param [Array<LZRTag::Player::Base] Array of active players

# File lib/lzrtag/handler/game_handler.rb, line 230
def gamePlayers=(newPlayers)
        raise ArgumentError, "Game player list shall be an array!" unless newPlayers.is_a? Array
        oldGamePlayers = @gamePlayers.dup
        @gamePlayers = newPlayers.dup;

        @playerNames = Array.new();
        plNameArray = Array.new();
        @gamePlayers.each do |pl|
                plNameArray << pl.DeviceID();
        end

        newPlayers = @gamePlayers - oldGamePlayers;
        newPlayers.each do |pl|
                send_event :playerEnteredGame, pl;
        end
        oldPlayers = oldGamePlayers - @gamePlayers;
        oldPlayers.each do |pl|
                send_event :playerLeftGame, pl;
        end

        @mqtt.publish_to "Lasertag/Game/ParticipatingPlayers", plNameArray.to_json(), retain: true
end
get_allowed_phases() click to toggle source

Returns an Array<Symbol> of the currently allowed phases of this game. This list can also be retrieved via MQTT, under Lasertag/Game/CurrentGame

# File lib/lzrtag/handler/game_handler.rb, line 192
def get_allowed_phases()
        allowedPhases = [:idle]
        if(@currentGame)
                allowedPhases = [allowedPhases, @currentGame.phases].flatten
        end

        return allowedPhases;
end
in_game?(player) click to toggle source

Check if a player is currently in game

# File lib/lzrtag/handler/game_handler.rb, line 261
def in_game?(player)
        return @gamePlayers.include? player
end
register_game(gameTag, game) click to toggle source

Register a game by a tag. This function will register a given LZRTag::Game::Base class under a given string tag. This tag can then be used to, via MQTT, start the game, and is also used to give players a cleartext game name. A list of games is published to Lasertag/Game/KnownGames @param gameTag [String] Cleartext name of the game @param game [LZRTag::Game::Base] The game class to register @see start_game

# File lib/lzrtag/handler/game_handler.rb, line 127
def register_game(gameTag, game)
        raise ArgumentError, "Game Tag must be a string!" unless gameTag.is_a? String
        raise ArgumentError, "Game must be a LZRTag::Game class" unless game <= LZRTag::Game::Base

        @knownGames[gameTag] = game;

        x_logi("Game registered: #{gameTag}");

        @mqtt.publish_to "Lasertag/Game/KnownGames", @knownGames.keys.to_json, retain: true;
end
set_phase(nextPhase) click to toggle source

Tries to change the current phase. This function will set the current phase to nextPhase, if it is an allowed one. However, if nextPhase does not belong to the list of allowed phases, and error is raised. The :gamePhaseEnds and :gamePhaseStarts events are triggered properly. This function can be called from any context, not just inside the game code itself. @see get_allowed_phases

# File lib/lzrtag/handler/game_handler.rb, line 208
def set_phase(nextPhase)
        allowedPhases = get_allowed_phases();

        raise ArgumentError, "Phase must be valid!" unless allowedPhases.include? nextPhase

        x_logi("Starting phase #{nextPhase}");

        oldPhase = @gamePhase
        send_event(:gamePhaseEnds, oldPhase, nextPhase)

        @mqtt.publish_to "Lasertag/Game/Phase/Current", @gamePhase.to_s, retain: true
        @gamePhase = nextPhase;
        send_event(:gamePhaseStarts, nextPhase, oldPhase);
end
start_game(game = @lastGame) click to toggle source

Starts a given new game (or the last one). This function will take either a String (as registered with register_game), or a LZRTag::Game::Base class, instantiate it, and start it. If no fitting game was found, the game is instead stopped.

@note The first phase that is started by default is :starting, the Game

class must define at least a phase_prep hook to change the phase and
configure the game!

@param game [String,LZRTag::Game::Base] The game, or game name, to start

# File lib/lzrtag/handler/game_handler.rb, line 155
def start_game(game = @lastGame)
        @lastGame = game;

        if(game.is_a? String and gClass = @knownGames[game])
                game = gClass;
        elsif(game.is_a? String)
                stop_game();
                return;
        end

        if(gKey = @knownGames.key(game))
                @mqtt.publish_to "Lasertag/Game/CurrentGame", gKey, retain: true
                x_logi("Starting game #{gKey}");
        else
                @mqtt.publish_to "Lasertag/Game/CurrentGame", "", retain: true
        end

        game = game.new(self) if game.is_a? Class and game <= LZRTag::Game::Base;
        unless(game.is_a? LZRTag::Game::Base)
                raise ArgumentError, "Game class needs to be specified!"
        end
        @nextGame = game;
        send_event(:gameStarting, @nextGame);

        @gameTickThread.run();
        @mqtt.publish_to "Lasertag/Game/Phase/Valid",
                get_allowed_phases.to_json(), retain: true
end
stop_game() click to toggle source

Stops the currently running game.

# File lib/lzrtag/handler/game_handler.rb, line 185
def stop_game()
        @nextGame = nil;
        @mqtt.publish_to "Lasertag/Game/CurrentGame", "", retain: true
end

Private Instance Methods

_start_game_thread() click to toggle source
# File lib/lzrtag/handler/game_handler.rb, line 91
def _start_game_thread()
        @gameTickThread = Thread.new() do
                loop do
                        Thread.stop() until(@nextGame.is_a? LZRTag::Game::Base);

                        @currentGame = @nextGame;
                        set_phase(:starting);

                        @lastTick = Time.now();
                        while(@currentGame == @nextGame)
                                sleep @currentGame.tickTime
                                dT = Time.now() - @lastTick;
                                @lastTick = Time.now();

                                send_event(:gameTick, dT);
                        end

                        x_logi("Stopping game!");

                        set_phase(:idle);
                        sleep 1;
                        @currentGame = nil;
                end
        end
        @gameTickThread.abort_on_exception = true;
end
clean_game_topics() click to toggle source
# File lib/lzrtag/handler/game_handler.rb, line 84
def clean_game_topics()
        @mqtt.publish_to "Lasertag/Game/ParticipatingPlayers", [].to_json(), retain: true
        @mqtt.publish_to "Lasertag/Game/KnownGames", [].to_json, retain: true;
        @mqtt.publish_to "Lasertag/Game/CurrentGame", "", retain: true
end