# encoding: utf-8 require_relative './util/constants' require_relative 'player' require_relative 'board' require_relative 'move' require_relative 'condition' require_relative 'field_type' # The state of a game, as received from the server. class GameState # @!attribute [rw] turn # @return [Integer] turn number attr_accessor :turn # @!attribute [rw] start_player_color # @return [PlayerColor] the start-player's color attr_accessor :start_player_color # @!attribute [rw] current_player_color # @return [PlayerColor] the current player's color attr_accessor :current_player_color # @!attribute [r] red # @return [Player] the red player attr_reader :red # @!attribute [r] blue # @return [Player] the blue player attr_reader :blue # @!attribute [rw] board # @return [Board] the game's board attr_accessor :board # @!attribute [rw] last_move # @return [Move] the last move performed attr_accessor :last_move # @!attribute [rw] condition # @return [Condition] the winner and winning reason attr_accessor :condition # @!attribute [rw] has_to_play_card # @return [Boolean] true if the current player has to play a card attr_accessor :has_to_play_card alias has_to_play_card? has_to_play_card def field(index) board.field(index) end def initialize @current_player_color = PlayerColor::RED @start_player_color = PlayerColor::RED @board = Board.new @has_to_play_card = false @turn = 0 end # adds a player to the gamestate # # @param player [Player] the player, that will be added def add_player(player) if player.color == PlayerColor::RED @red = player elsif player.color == PlayerColor::BLUE @blue = player end end # gets the current player # # @return [Player] the current player def current_player return red if current_player_color == PlayerColor::RED return blue if current_player_color == PlayerColor::BLUE end # gets the other (not the current) player # # @return [Player] the other (not the current) player def other_player return blue if current_player_color == PlayerColor::RED return red if current_player_color == PlayerColor::BLUE end # gets the other (not the current) player's color # # @return [PlayerColor] the other (not the current) player's color def other_player_color PlayerColor.opponent_color(current_player_color) end # gets the current round # # @return [Integer] the current round def round turn / 2 end # performs a move on the gamestate # # @param move [Move] the move, that will be performed # @param player [Player] the player, who makes the move def perform!(move, player) move.actions.each do |action| action.perform!(self, player) end end # has the game ended? # # @return [Boolean] true, if the game has allready ended def game_ended? !condition.nil? end # gets the game's winner # # @return [Player] the game's winner def winner condition.nil? ? nil : condition.winner end # gets the winning reason # # @return [String] the winning reason def winning_reason condition.nil? ? nil : condition.reason end # calculates a player's points based on the current gamestate # # @param player [Player] the player, whos points to calculate # @return [Integer] the points of the player def points_for_player(player) player.index end # @return [Boolean] true if the given field is occupied by the other (not # current) player. def occupied_by_other_player?(field) field.index == other_player.index end # Searches backwards starting at the field with the given index. The field # at the given index is not considered. If no field of the given type exists, # nil is returned. # # @param [FieldType] type of the field to search for # @param [Integer] index before which field-index to start searching # @return [Field] the next field before the given index of given type def previous_field_of_type(type, index) return nil if index < 1 return nil if index >= board.fields.size board.fields.slice(0..(index - 1)).reverse.find {|f| f.type == type} end # Searches forward starting at the field with the given index. The field # at the given index is not considered. If no field of the given type exists, # nil is returned. # # @param [FieldType] type of the field to search for # @param [Integer] index after which field-index to start searching # @return [Field] the next field after the given index of given type def next_field_of_type(type, index) return nil if index >= board.fields.size return nil if index < 0 board.fields.slice((index + 1)..(board.fields.size - 1)).find {|f| f.type == type} end # Compared with other state. def ==(other) turn == other.turn && start_player_color == other.start_player_color && current_player_color == other.current_player_color && red == other.red && blue == other.blue && board == other.board && lastMove == other.lastMove && has_to_play_card == other.has_to_play_card && condition == other.condition end # Create a deep copy of the gamestate. Can be used to perform moves on without # changing the original gamestate. def deep_clone Marshal.load(Marshal.dump(self)) end def set_last_action(action) return if action.instance_of? Skip current_player.last_non_skip_action = action end def current_field field(current_player.index) end def is_first(player) if PlayerColor.opponent_color(player.color) == PlayerColor::RED player.index > red.index else player.index > blue.index end end def is_second(player) !is_first(player) end def switch_current_player current_player_color = other_player_color end def possible_moves found_moves = [] if GameRules.is_valid_to_eat(self)[0] # Wenn ein Salat gegessen werden kann, muss auch ein Salat gegessen werden found_moves << Move.new([EatSalad.new]) return found_moves end if GameRules.is_valid_to_exchange_carrots(self, 10)[0] found_moves << Move.new([ExchangeCarrots.new(10)]) end if GameRules.is_valid_to_exchange_carrots(self, -10)[0] found_moves << Move.new([ExchangeCarrots.new(-10)]) end if GameRules.is_valid_to_fall_back(self)[0] found_moves << Move.new([FallBack.new]) end # Generiere mögliche Vorwärtszüge (1..(GameRules.calculate_movable_fields(self.current_player.carrots))).each do |distance| actions = [] clone = self.deep_clone # Überprüfe ob Vorwärtszug möglich ist if GameRules.is_valid_to_advance(clone, distance)[0] try_advance = Advance.new(distance) try_advance.perform!(clone) actions << try_advance # überprüfe, ob eine Karte gespielt werden muss/kann if GameRules.must_play_card(clone) clone.check_for_playable_cards(actions).each do |card| found_moves << card # TODO: this is unexpected, rename or refactor end else # Füge möglichen Vorwärtszug hinzu found_moves << Move.new(actions) end end end if found_moves.empty? found_moves << Move.new([Skip.new]) end found_moves end # @return [Array[Move]] gibt liste von Zuegen zurueck, die gemacht werden koennen, vorausgesetzt die gegebene Liste von Aktionen wurde schon gemacht. def check_for_playable_cards(actions) found_card_playing_moves = [] if current_player.must_play_card if GameRules.is_valid_to_play_eat_salad(self)[0] found_card_playing_moves << Move.new(actions + [Card.new(CardType::EAT_SALAD)]) end [20, -20, 0].each do |carrots| if GameRules.is_valid_to_play_take_or_drop_carrots(self,carrots)[0] found_card_playing_moves << Move.new(actions + [Card.new(CardType::TAKE_OR_DROP_CARROTS, carrots)]) end end if GameRules.is_valid_to_play_hurry_ahead(self)[0] actions_with_card_played = actions + [Card.new(CardType::HURRY_AHEAD)] # pruefe, ob auf Hasenfeld gelandet clone = self.deep_clone Card.new(CardType::HURRY_AHEAD).perform!(clone) moves = [] if GameRules.must_play_card(clone) moves = clone.check_for_playable_cards(actions_with_card_played) end if moves.empty? found_card_playing_moves << Move.new(actions_with_card_played) else found_card_playing_moves += moves end end if GameRules.is_valid_to_play_fall_back(self)[0] actions_with_card_played = actions + [Card.new(CardType::FALL_BACK)] # pruefe, ob auf Hasenfeld gelandet clone = self.deep_clone Card.new(CardType::FALL_BACK).perform!(clone) moves = [] if GameRules.must_play_card(clone) moves = clone.check_for_playable_cards(actions_with_card_played) end if moves.empty? found_card_playing_moves << Move.new(actions_with_card_played) else found_card_playing_moves += moves end end end found_card_playing_moves end end