# encoding: utf-8 # An action is a part of a move. A move can have multiple actions. The specific # actions are inherited from this Action class which should be considered # abstract/interface. class Action # @return [ActionType] Type of the action. def type raise 'must be overridden' end def ==(_other) raise 'must be overridden' end def perform!(_gamestate) raise 'must be overridden' end # Helper to make raising InvalidMoveExceptions easier. It is defined in the # Action class instead of the Move class because performing individual actions # normally trigger invalid moves, not the move itself. # # @param message [String] Message why the move is invalid. # @return Nothing. Raises an exception. def invalid(message) raise InvalidMoveException.new(message, self) end end # Ein Vorwärtszug, um spezifizierte Distanz. Verbrauchte Karroten werden mit k = # (distance * (distance + 1)) / 2 berechnet (Gaußsche Summe) class Advance < Action attr_reader :distance def initialize(distance) @distance = distance end # Perform the action. # # @param gamestate [GameState] The game state on which the action will be # performed. Performing may change the game state. The action is performed for # the current player of the game state. def perform!(gamestate) valid, message = GameRules.is_valid_to_advance(gamestate, distance) invalid(message) unless valid # perform state changes required_carrots = distance * (distance + 1) / 2 gamestate.current_player.carrots -= required_carrots gamestate.current_player.index += distance if gamestate.current_field.type == FieldType::HARE gamestate.current_player.must_play_card = true end end def type :advance end def ==(other) other.type == type && other.distance == distance end end # Play a card. class Card < Action # only for type TAKE_OR_DROP_CARROTS attr_reader :value attr_reader :card_type def initialize(card_type, value = 0) @card_type = card_type @value = value end # (see Advance#perform!) def perform!(gamestate) gamestate.current_player.must_play_card = false case card_type when CardType::EAT_SALAD valid, message = GameRules.is_valid_to_play_eat_salad(gamestate) invalid("Das Ausspielen der EAT_SALAD Karte ist nicht möglich. " + message) unless valid gamestate.current_player.salads -= 1 if gamestate.is_first(gamestate.current_player) gamestate.current_player.carrots += 10 else gamestate.current_player.carrots += 20 end when CardType::FALL_BACK valid, message = GameRules.is_valid_to_play_fall_back(gamestate) invalid("Das Ausspielen der FALL_BACK Karte ist nicht möglich. " + message) unless valid gamestate.current_player.index = gamestate.other_player.index - 1 if gamestate.field(gamestate.current_player.index).type == FieldType::HARE gamestate.current_player.must_play_card = true end when CardType::HURRY_AHEAD valid, message = GameRules.is_valid_to_play_hurry_ahead(gamestate) invalid("Das Ausspielen der HURRY_AHEAD Karte ist nicht möglich. " + message) unless valid gamestate.current_player.index = gamestate.other_player.index + 1 if gamestate.field(gamestate.current_player.index).type == FieldType::HARE gamestate.current_player.must_play_card = true end when CardType::TAKE_OR_DROP_CARROTS valid, message = GameRules.is_valid_to_play_take_or_drop_carrots(gamestate, value) invalid("Das Ausspielen der TAKE_OR_DROP_CARROTS Karte ist nicht möglich. " + message) unless valid gamestate.current_player.carrots += value else raise "Unknown card type #{card_type.inspect}!" end gamestate.set_last_action(self) gamestate.current_player.cards.delete(self.type) end def type :card end def ==(other) other.card_type == card_type && (card_type != CardType::TAKE_OR_DROP_CARROTS || (other.value == value)) end end # Ein Aussetzzug. Ist nur erlaubt, sollten keine anderen Züge möglich sei class Skip < Action def initialize() end def type :skip end def ==(other) other.type == type end end # Eine Salatessen-Aktion. Kann nur auf einem Salatfeld ausgeführt werden. Muss ausgeführt werden, # ein Salatfeld betreten wird. Nachdem die Aktion ausgefürht wurde, muss das Salatfeld verlassen # werden, oder es muss ausgesetzt werden. # Duch eine Salatessen-Aktion wird ein Salat verbraucht und es werden je nachdem ob der Spieler führt # oder nicht 10 oder 30 Karotten aufgenommen. class EatSalad < Action def initialize() end def type :eat_salad end def ==(other) other.type == type end end # Karottentauschaktion. Es können auf einem Karottenfeld 10 Karotten abgegeben oder aufgenommen werden. # Dies kann beliebig oft hintereinander ausgeführt werden. class ExchangeCarrots < Action attr_accessor :value def initialize(value) @value = value end def perform!(gamestate) valid, message = GameRules.is_valid_to_exchange_carrots(gamestate, value) raise InvalidMoveException("Es können nicht #{value} Karotten aufgenommen werden. " + message) unless valid gamestate.current_player.carrots += value gamestate.set_last_action(self) end def type :exchange_carrots end def ==(other) other.type == type && other.value == value end end class FallBack < Action def perform!(gamestate) valid, message = GameRules.is_valid_to_fall_back(gamestate) raise InvalidMoveException("Es kann gerade kein Rückzug gemacht werden. " + message) unless valid gamestate.current_player.index = gamestate.previous_field_of_type(FieldType::HEDGEHOG, gamestate.current_player.index).index gamestate.set_last_action(self) end def type :fall_back end def ==(other) other.type == type end end