Class: GameRules

Inherits:
Object
  • Object
show all
Defined in:
lib/software_challenge_client/game_rules.rb

Overview

All methods which define the game rules. Needed for checking validity of moves and performing them.

Class Method Summary collapse

Class Method Details

.calculate_carrots(moveCount) ⇒ Object

Berechnet wie viele Karotten für einen Zug der länge moveCount benötigt werden.

Parameters:

  • moveCount

    Anzahl der Felder, um die bewegt wird

Returns:

  • Anzahl der benötigten Karotten



15
16
17
# File 'lib/software_challenge_client/game_rules.rb', line 15

def self.calculate_carrots(moveCount)
  (moveCount * (moveCount + 1)) / 2
end

.calculate_movable_fields(carrots) ⇒ Object

Berechnet, wieviele Züge mit carrots Karotten möglich sind.

Parameters:

  • carrots

    maximal ausgegebene Karotten

Returns:

  • Felder um die maximal bewegt werden kann



23
24
25
26
27
28
# File 'lib/software_challenge_client/game_rules.rb', line 23

def self.calculate_movable_fields(carrots)
  # bei 30 Runden koennen nur 990 Karotten gesammelt werden
  return 44 if carrots >= 990
  return 0 if carrots < 1
  (Math.sqrt(2 * carrots + 1/4) - 1/2).round
end

.can_advance_to_any_field(state) ⇒ Object

Überprüft ob der derzeitige Spieler zu irgendeinem Feld einen Vorwärtszug machen kann.

Parameters:

  • state

    GameState

Returns:

  • true, falls der Spieler irgendeinen Vorwärtszug machen kann



95
96
97
98
99
100
# File 'lib/software_challenge_client/game_rules.rb', line 95

def self.can_advance_to_any_field(state)
  fields = calculate_movable_fields(state.getCurrentPlayer().getCarrots())
  (0..fields).to_a.any? do |i|
    is_valid_to_advance(state, i)[0]
  end
end

.can_do_anything(state) ⇒ Object

Überprüft, ob ein Spieler einen Zug (keinen Aussetzug)

Parameters:

  • state

    GameState

Returns:

  • true, falls ein Zug möglich ist.



85
86
87
88
89
90
# File 'lib/software_challenge_client/game_rules.rb', line 85

def self.can_do_anything(state)
  return can_play_any_card(state) || is_valid_to_fall_back(state)[0] ||
         is_valid_to_exchange_carrots(state, 10)[0] ||
         is_valid_to_exchange_carrots(state, -10)[0] ||
         is_valid_to_eat(state)[0] || can_advance_to_any_field(state)
end

.can_enter_goal(state) ⇒ Object

Überprüft ob ein der derzeitige Spieler das Ziel betreten darf

Parameters:

  • state

    GameState

Returns:



373
374
375
376
# File 'lib/software_challenge_client/game_rules.rb', line 373

def self.can_enter_goal(state)
  player = state.current_player
  player.carrots <= 10 && player.salads == 0
end

.can_move(state) ⇒ Object

TODO difference isVAlidTOMove

Parameters:

  • state

Returns:



353
354
355
356
357
358
359
360
# File 'lib/software_challenge_client/game_rules.rb', line 353

def self.can_move(state)
  can_move = false
  max_distance = GameRules.calculate_movable_fields(state.getCurrentPlayer().getCarrots())
  (1..max_distance).to_a.each do |i|
    can_move = can_move || isValidToAdvance(state, i)
  end
  return can_move
end

.can_play_any_card(state) ⇒ Object

Überprüft ob der derzeitige Spieler irgendeine Karte spielen kann. TAKE_OR_DROP_CARROTS wird nur mit 20 überprüft

Parameters:

  • state

    GameState

Returns:

  • true, falls das Spielen einer Karte möglich ist



280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
# File 'lib/software_challenge_client/game_rules.rb', line 280

def self.can_play_any_card(state)
  valid = false
  player = state.current_player

  player.cards.any? do |card|
    case card
      when CardType::EAT_SALAD
        is_valid_to_play_eat_salad(state)[0]
      when CardType::FALL_BACK
        is_valid_to_play_fall_back(state)[0]
      when CardType::HURRY_AHEAD
        is_valid_to_play_hurry_ahead(state)[0]
      when CardType::TAKE_OR_DROP_CARROTS
        is_valid_to_play_take_or_drop_carrots(state, 20)[0]
      else
        raise "Unknown CardType " + card
    end
  end
end

.can_play_card(state) ⇒ Object

TODO difference isValidToPlayCard

Parameters:

  • state

Returns:



341
342
343
344
345
346
347
348
# File 'lib/software_challenge_client/game_rules.rb', line 341

def self.can_play_card(state)
  player = state.getCurrentPlayer()
  canPlayCard = state.getTypeAt(player.getFieldIndex()).equals(FieldType.HARE)
  player.getCards().each do |card|
    canPlayCard = canPlayCard || is_valid_to_play_card(state, card, 0)
  end
  return canPlayCard
end

.is_valid_to_advance(state, distance) ⇒ true, ''

Überprüft Advance Aktionen auf ihre Korrektheit. Folgende Spielregeln werden beachtet:

  • Der Spieler muss genügend Karotten für den Zug besitzen

  • Wenn das Ziel erreicht wird, darf der Spieler nach dem Zug maximal 10 Karotten übrig haben

  • Man darf nicht auf Igelfelder ziehen

  • Salatfelder dürfen nur betreten werden, wenn man noch Salate essen muss

  • Hasenfelder dürfen nur betreten werden, wenn man noch Hasenkarten ausspielen kann

Parameters:

  • state

    GameState

  • distance

    relativer Abstand zur aktuellen Position des Spielers

Returns:

  • (true, '')

    falls ein Vorwärtszug möglich ist, [false, M] falls nicht, wobei M ein String mit einer Begruendung ist



42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
# File 'lib/software_challenge_client/game_rules.rb', line 42

def self.is_valid_to_advance(state, distance)
  return false, 'Ein Vorwärtszug benötigt eine Mindestdistanz von einem Feld.' if distance <= 0
  player = state.current_player
  return false, 'Es muss ein Salat gegessen werden, bevor ein Vorwärtszug gemacht werden kann.' if must_eat_salad(state)
  required_carrots = GameRules.calculate_carrots(distance)
  return false, "Nicht genug Karotten, um #{distance} Felder vorwärts zu ziehen (Vorrat: #{player.carrots}, benötigt: #{required_carrots}" if (required_carrots > player.carrots)
  new_position = player.index + distance
  return false, 'Zielfeld wird von anderem Spieler besetzt.' if state.occupied_by_other_player?(state.field(new_position))
  case state.field(new_position).type
    when FieldType::INVALID
      return false, "Zielfeld #{new_position} ist nicht vorhanden."
    when FieldType::SALAD
      return false, 'Ohne Salat darf ein Salatfeld nicht betreten werden.' if player.salads < 1
    when FieldType::HARE
      state2 = state.deep_clone
      state2.set_last_action(Advance.new(distance))
      state2.current_player.index = new_position
      state2.current_player.carrots -= required_carrots
      return false, 'Auf ein Hasenfeld darf nur gezogen werden, wenn eine Karte gespielt werden kann' unless can_play_any_card(state2)
    when FieldType::GOAL
      carrotsLeft = player.carrots - required_carrots
      return false, "Auf das Zielfeld darf nur mit maximal 10 Karotten gezogen werden (es sind aber #{carrotsLeft} bei Erreichen des Zielfeldes)." unless carrotsLeft <= 10
      return false, "Auf das Zielfeld darf nur ohne Salate gezogen werden (es sind aber #{player.salads} übrig)." unless player.salads == 0
    when FieldType::HEDGEHOG
      return false, 'Auf ein Igelfeld darf nicht vorwärts gezogen werden.'
    when FieldType::CARROT, FieldType::POSITION_1, FieldType::START, FieldType::POSITION_2
      return true, ''
    else
      raise "Unknown Type #{state.field(new_position).type.inspect}"
  end
  return true, ''
end

.is_valid_to_eat(state) ⇒ true, ''

Überprüft EatSalad Züge auf Korrektheit. Um einen Salat zu verzehren muss der Spieler sich:

  • auf einem Salatfeld befinden

  • noch mindestens einen Salat besitzen

  • vorher kein Salat auf diesem Feld verzehrt wurde

Parameters:

  • state

    GameState

Returns:

  • (true, '')

    , falls ein Salad gegessen werden darf, [false, M] falls nicht, wobei M ein String mit dem Grund ist



111
112
113
114
115
116
# File 'lib/software_challenge_client/game_rules.rb', line 111

def self.is_valid_to_eat(state)
  return false, 'Salate dürfen nur auf Salatfeldern gegessen werden.' unless state.current_field.type == FieldType::SALAD
  return false, 'Spieler hat keine Salate mehr zum Essen.' if state.current_player.salads < 1
  return false, 'Spieler muss das Feld verlassen.' if player_must_advance(state)
  return true, ''
end

.is_valid_to_exchange_carrots(state, n) ⇒ true, ''

Überprüft ob der derzeitige Spieler 10 Karotten nehmen oder abgeben kann.

Parameters:

  • state

    GameState

  • n

    10 oder -10 je nach Fragestellung

Returns:

  • (true, '')

    , falls die durch n spezifizierte Aktion möglich ist, [false, M] falls nicht, wobei M ein String mit dem Grund ist



149
150
151
152
153
154
155
156
# File 'lib/software_challenge_client/game_rules.rb', line 149

def self.is_valid_to_exchange_carrots(state, n)
  player = state.current_player
  return false, "Karotten können nur auf einem Karottenfeld #{n > 0 ? 'genommen' : 'abgegeben'} werden" if state.board.field(player.index).type != FieldType::CARROT
  return true, '' if n == 10
  return false, 'Gültige Karottenzahlen sind 10 und -10.' unless n == -10
  return false, "Spieler hat keine 10 Karotten zum Abgeben (er hat #{player.carrots})." if player.carrots < 10
  return true, ''
end

.is_valid_to_fall_back(state) ⇒ true, ''

Überprüft FallBack Züge auf Korrektheit

Parameters:

  • state

    GameState

Returns:

  • (true, '')

    , falls der currentPlayer einen Rückzug machen darf, [false, M] falls nicht, wobei M ein String mit dem Grund ist.



162
163
164
165
166
167
168
169
170
# File 'lib/software_challenge_client/game_rules.rb', line 162

def self.is_valid_to_fall_back(state)
  return false, 'Spieler muss einen Salat fressen.' if must_eat_salad(state)
  target_field = state.previous_field_of_type(
    FieldType::HEDGEHOG, state.current_player.index
  )
  return false, 'Es gibt kein Igelfeld hinter dem Spieler.' if target_field.nil?
  return false, 'Das Igelfeld hinter dem Spieler ist besetzt.' if state.occupied_by_other_player?(target_field)
  return true, ''
end

.is_valid_to_play_card(state, c, n = 0) ⇒ Object

Überprüft ob der derzeitige Spieler die Karte spielen kann.

Parameters:

  • state
  • c

    Karte die gespielt werden soll

  • n (defaults to: 0)

    Parameter mit dem TAKE_OR_DROP_CARROTS überprüft wird, default 0

Returns:

  • true, falls das Spielen der entsprechenden karte möglich ist



305
306
307
308
309
310
311
312
313
314
315
316
317
318
# File 'lib/software_challenge_client/game_rules.rb', line 305

def self.is_valid_to_play_card(state, c, n = 0)
  case c
    when CardType::EAT_SALAD
      isValidToPlayEatSalad(state)[0]
    when CardType::FALL_BACK
      is_valid_to_play_fall_back(state)[0]
    when CardType::HURRY_AHEAD
      is_valid_to_play_hurry_ahead(state)[0]
    when CardType::TAKE_OR_DROP_CARROTS
      is_valid_to_play_take_or_drop_carrots(state, n)[0]
    else
      raise "Unknown CardType " + c
  end
end

.is_valid_to_play_eat_salad(state) ⇒ true, ''

Überprüft ob der derzeitige Spieler die EAT_SALAD Karte spielen darf.

Parameters:

  • state

    GameState

Returns:

  • (true, '')

    , falls die EAT_SALAD Karte gespielt werden darf, (false, M) falls nicht, wobei M ein String mit dem Grund ist



267
268
269
270
271
272
273
274
# File 'lib/software_challenge_client/game_rules.rb', line 267

def self.is_valid_to_play_eat_salad(state)
  player = state.current_player
  return false, 'Es muss vorwärts gezogen werden.' if player_must_advance(state)
  return false, 'Karten können nur auf Hasenfeldern gespielt werden.' unless state.current_field.type == FieldType::HARE
  return false, 'Spieler besitzt die Karte nicht.' unless player.owns_card_of_type(CardType::EAT_SALAD)
  return false, 'Spieler hat keine Salate zum Essen' if player.salads < 1
  return true, ''
end

.is_valid_to_play_fall_back(state) ⇒ true, ''

Überprüft ob der derzeitige Spieler die FALL_BACK Karte spielen darf.

Parameters:

  • state

    GameState

Returns:

  • (true, '')

    , falls die FALL_BACK Karte gespielt werden darf, [false, M] falls nicht, wobei M ein String mit dem Grund ist.



175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
# File 'lib/software_challenge_client/game_rules.rb', line 175

def self.is_valid_to_play_fall_back(state)
  player = state.current_player
  return false, 'Spieler muss einen Vorwärtszug machen.' if player_must_advance(state)
  return false, 'Karten können nur auf Hasenfeldern gespielt werden.' unless state.current_field.type == FieldType::HARE
  return false, 'Nur der erste Spieler darf die FALL_BACK Karte spielen.' unless state.is_first(player)
  return false, 'Spieler besitzt die Karte FALL_BACK nicht.' unless player.owns_card_of_type(CardType::FALL_BACK)

  next_pos = state.other_player.index - 1

  case state.field(next_pos).type
    when FieldType::INVALID
      return false, 'Durch Spielen der FALL_BACK Karte darf man nicht auf einem nicht vorhandenen Feld landen (also vor dem Start).'
    when FieldType::HEDGEHOG
      return false, 'Durch Spielen der FALL_BACK Karte darf man nicht auf einem Igelfeld landen.'
    when FieldType::SALAD
      return false, 'Spieler käme durch Spielen der FALL_BACK Kart auf ein Salatfeld, hat aber keine Salate.' if player.salads < 1
    when FieldType::HARE
      state2 = state.deep_clone
      state2.set_last_action(Card.new(CardType::HURRY_AHEAD))
      state2.current_player.cards.delete(CardType::FALL_BACK)
      return false, 'Spieler käme durch Spielen der FALL_BACK Kart auf ein Hasenfeld, kann aber dann keine weitere Karte mehr spielen.' unless can_play_any_card(state2)
    when FieldType::START
    when FieldType::CARROT
    when FieldType::POSITION_1
    when FieldType::POSITION_2
      return true, ''
    when FieldType::GOAL
      raise 'Player got onto goal by playing a fall back card. This should never happen.'
    else
      raise "Unknown Type #{state.field(next_pos).type.inspect}"
  end
  return true, ''
end

.is_valid_to_play_hurry_ahead(state) ⇒ true, ''

Überprüft ob der derzeitige Spieler die HURRY_AHEAD Karte spielen darf.

Parameters:

  • state

    GameState

Returns:

  • (true, '')

    , falls die HURRY_AHEAD Karte gespielt werden darf, [false, M], falls nicht, wobei M ein String mit dem Grund ist.



212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
# File 'lib/software_challenge_client/game_rules.rb', line 212

def self.is_valid_to_play_hurry_ahead(state)
  player = state.current_player
  return false, 'Spieler muss einen Vorwärtszug machen.' if player_must_advance(state)
  return false, 'Karten können nur auf Hasenfeldern gespielt werden.' unless state.current_field.type == FieldType::HARE
  return false, 'Nur der zweite Spieler darf die HURRY_AHEAD Karte spielen.' unless state.is_second(player)
  return false, 'Spieler besitzt die Karte HURRY_AHEAD nicht.' unless player.owns_card_of_type(CardType::HURRY_AHEAD)

  o = state.other_player
  next_pos = o.index + 1

  case state.field(next_pos).type
    when FieldType::INVALID
      return false, 'Durch Spielen der HURRY_AHEAD Karte darf man nicht auf einem nicht vorhandenen Feld landen (also nach dem Ziel).'
    when FieldType::HEDGEHOG
      return false, 'Durch Spielen der HURRY_AHEAD Karte darf man nicht auf einem Igelfeld landen.'
    when FieldType::SALAD
      return false, 'Spieler käme durch Spielen der HURRY_AHEAD Kart auf ein Salatfeld, hat aber keine Salate.' if player.salads < 1
    when FieldType::HARE
      state2 = state.deep_clone
      state2.set_last_action(Card.new(CardType::HURRY_AHEAD))
      state2.current_player.cards.delete(CardType::HURRY_AHEAD)
      return false, 'Spieler käme durch Spielen der HURRY_AHEAD Kart auf ein Hasenfeld, kann aber dann keine weitere Karte mehr spielen.' unless can_play_any_card(state2)
    when FieldType::GOAL
      return false, 'Spieler käme durch Spielen der HURRY_AHEAD Kart ins Ziel, darf es aber nicht betreten (entweder noch Salate oder mehr als 10 Karotten).' unless can_enter_goal(state)
    when FieldType::CARROT
    when FieldType::POSITION_1
    when FieldType::POSITION_2
      return true, ''
    when FieldType::START
      raise 'Player got onto start field by playing a hurry ahead card. This should never happen.'
    else
      raise "Unknown Type #{state.field(next_pos).type.inspect}"
  end
  return true, ''
end

.is_valid_to_play_take_or_drop_carrots(state, n) ⇒ true, ''

Überprüft ob der derzeitige Spieler die TAKE_OR_DROP_CARROTS Karte spielen darf.

Parameters:

  • state

    GameState

  • n

    20 für nehmen, -20 für abgeben, 0 für nichts tun

Returns:

  • (true, '')

    , falls die TAKE_OR_DROP_CARROTS Karte gespielt werden darf, [false, M], falls nicht, wobei M ein String mit dem Grund ist.



252
253
254
255
256
257
258
259
260
261
262
# File 'lib/software_challenge_client/game_rules.rb', line 252

def self.is_valid_to_play_take_or_drop_carrots(state, n)
  player = state.current_player
  return false, 'Spieler muss einen Vorwärtszug machen.' if player_must_advance(state)
  return false, 'Karten können nur auf Hasenfeldern gespielt werden.' unless state.current_field.type == FieldType::HARE
  return false, 'Spieler besitzt die Karte TAKE_OR_DROP_CARROTS nicht.' unless player.owns_card_of_type(CardType::TAKE_OR_DROP_CARROTS)
  return false, "#{n} ist keine erlaubte Anzahl beim Spielen der Karte TAKE_OR_DROP_CARROTS (erlaubt sind 20, -20 und 0)." unless [20, -20, 0].include?(n)
  return true, '' if n >= 0
  # at this point, n has to be -20
  return false, "Spieler hat keine 20 Karotten zum Abgeben (er hat #{player.carrots})." if player.carrots < 20
  return true, ''
end

.is_valid_to_skip(state) ⇒ Object

Überprüft, ob ein Spieler aussetzen darf. Er darf dies, wenn kein anderer Zug möglich ist.

Parameters:

  • state

    GameState

Returns:

  • true, falls der derzeitige Spieler keine andere Aktion machen kann.



78
79
80
# File 'lib/software_challenge_client/game_rules.rb', line 78

def self.is_valid_to_skip(state)
  return !GameRules.can_do_anything(state)
end

.must_eat_salad(state) ⇒ Object



320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
# File 'lib/software_challenge_client/game_rules.rb', line 320

def self.must_eat_salad(state)
  player = state.current_player
  # check whether player just moved to salad field and must eat salad
  field = state.board.field(player.index)
  if field.type == FieldType::SALAD
    if player.last_non_skip_action&.instance_of?(Advance)
      return true
    elsif player.last_non_skip_action&.instance_of?(Card)
      card_action = player.last_non_skip_action
      if card_action.card_type == CardType::FALL_BACK ||
          card_action.card_type == CardType::HURRY_AHEAD
        return true
      end
    end
  end
  return false
end

.must_play_card(state) ⇒ Object

Überprüft ob eine Karte gespielt werden muss. Sollte nach einem Zug eines Spielers immer false sein, ansonsten ist Zug ungültig.

Parameters:

  • state

    derzeitiger GameState



365
366
367
# File 'lib/software_challenge_client/game_rules.rb', line 365

def self.must_play_card(state)
  state.current_player.must_play_card
end

.player_must_advance(state) ⇒ Object

Überprüft ab der derzeitige Spieler im nächsten Zug einen Vorwärtszug machen muss.

Parameters:

  • state

    GameState

Returns:

  • true, falls der derzeitige Spieler einen Vorwärtszug gemacht werden muss



121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
# File 'lib/software_challenge_client/game_rules.rb', line 121

def self.player_must_advance(state)
  player = state.current_player
  type = state.board.field(player.index).type

  return true if (type == FieldType::HEDGEHOG || type == FieldType::START)

  last_action = state.current_player.last_non_skip_action

  if (!last_action.nil?)
    if (last_action.instance_of? EatSalad)
      return true
    elsif (last_action.instance_of? Card)
      # the player has to leave a rabbit field in next turn
      if (last_action.type == CardType::EAT_SALAD)
        return true
      elsif (last_action.type == CardType::TAKE_OR_DROP_CARROTS) # the player has to leave the rabbit field
        return true
      end
    end
  end

  return false
end