software_challenge_client 0.3.4 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,101 +1,45 @@
1
- # encoding: UTF-8
1
+ # encoding: utf-8
2
2
  require_relative './util/constants'
3
3
  require_relative 'game_state'
4
4
  require_relative 'player'
5
5
  require_relative 'field_type'
6
6
  require_relative 'field'
7
7
 
8
- # A representation of a mississippi queen game board
8
+ # Ein Spielbrett bestehend aus 65 Feldern.
9
9
  class Board
10
-
11
10
  # @!attribute [r] fields
12
- # @note Better use {#field} to access fields.
13
- # @return [Hash<Field>] A field will be stored at the hash of the
14
- # coordinate-tuple (2-element-array) of the field.
11
+ # @note Besser über die {#field} Methode auf Felder zugreifen.
12
+ # @return [Array<Field>] Ein Feld wird an der Position entsprechend seines
13
+ # Index im Array gespeichert.
15
14
  attr_reader :fields
16
15
 
17
16
  # Initializes the board
18
17
  def initialize
19
- @fields = {}
18
+ @fields = []
20
19
  end
21
20
 
22
21
  def to_s
23
- fields.values.map { |f| f.type.key.to_s[0] }.join(' ')
22
+ fields.map { |f| f.type.value }.join(' ')
24
23
  end
25
24
 
26
25
  def ==(other)
27
- fields.each_with_index do |row, x|
28
- row.each_with_index do |field, y|
29
- return false if field != other.field(x, y)
30
- end
26
+ fields.each_with_index do |field, index|
27
+ return false if field != other.field(index)
31
28
  end
32
29
  true
33
30
  end
34
31
 
35
32
  def add_field(field)
36
- fields[[field.x, field.y]] = field
37
- end
38
-
39
- # @return [Integer, Integer] The coordinates of the neighbor of the field
40
- # specified by given coordinates in specified
41
- # direction.
42
- def get_neighbor(x, y, direction)
43
- directions = {
44
- even_row: {
45
- Direction::RIGHT.key=> [+1, 0],
46
- Direction::UP_RIGHT.key=>[+1, -1],
47
- Direction::UP_LEFT.key=>[0, -1],
48
- Direction::LEFT.key=>[-1, 0],
49
- Direction::DOWN_LEFT.key=>[0, +1],
50
- Direction::DOWN_RIGHT.key=>[+1, +1]
51
- },
52
- odd_row: {
53
- Direction::RIGHT.key=> [+1, 0],
54
- Direction::UP_RIGHT.key=> [ 0, -1],
55
- Direction::UP_LEFT.key=> [-1, -1],
56
- Direction::LEFT.key=> [-1, 0],
57
- Direction::DOWN_LEFT.key=> [-1, +1],
58
- Direction::DOWN_RIGHT.key=> [ 0, +1]
59
- }
60
- }
61
-
62
- parity = y.odd? ? :odd_row : :even_row
63
- dir = directions[parity][direction.key]
64
- return x + dir[0], y + dir[1]
65
- end
66
-
67
- # @return [Field] The field in given direction with given distance from the
68
- # field with given coordinates.
69
- def get_in_direction(from_x, from_y, direction, distance = 1)
70
- x = from_x
71
- y = from_y
72
- distance.times do
73
- x, y = get_neighbor(x, y, direction)
74
- end
75
- if !field(x, y).nil?
76
- return field(x, y)
77
- else
78
- raise FieldUnavailableException.new(x, y)
79
- end
80
- end
81
-
82
- # @return [Array<Field>] A list of fields in given direction up to given
83
- # distance from the field with given coordinates.
84
- # The start field is excluded.
85
- def get_all_in_direction(from_x, from_y, direction, distance = 1)
86
- (1..distance).to_a.map do |i|
87
- get_in_direction(
88
- from_x, from_y, direction, i
89
- )
90
- end
33
+ @fields[field.index] = field
91
34
  end
92
35
 
93
- # Access fields of the board.
36
+ # Zugriff auf die Felder des Spielfeldes
94
37
  #
95
- # @param x [Integer] The x-coordinate of the field.
96
- # @param y [Integer] The y-coordinate of the field.
97
- # @return [Field] the field at the given coordinates.
98
- def field(x, y)
99
- fields[[x,y]]
38
+ # @param index [Integer] Der Index des Feldes
39
+ # @return [Field] Das Feld mit dem gegebenen Index. Falls das Feld nicht
40
+ # exisitert (weil der Index ausserhalb von 0..64 liegt), wird ein neues
41
+ # Feld vom Typ INVALID zurückgegeben.
42
+ def field(index)
43
+ fields.fetch(index, Field.new(FieldType::INVALID, index))
100
44
  end
101
45
  end
@@ -0,0 +1,13 @@
1
+ # encoding: utf-8
2
+
3
+ require 'typesafe_enum'
4
+ class CardType < TypesafeEnum::Base
5
+ # Nehme Karotten auf, oder leg sie ab
6
+ new :TAKE_OR_DROP_CARROTS
7
+ # Iß sofort einen Salat
8
+ new :EAT_SALAD
9
+ # Falle eine Position zurück
10
+ new :FALL_BACK
11
+ # Rücke eine Position vor
12
+ new :HURRY_AHEAD
13
+ end
@@ -1,65 +1,31 @@
1
1
  # encoding: UTF-8
2
2
  require_relative 'field_type'
3
3
 
4
- # A field on the game board.
4
+ # Ein Feld des Spielfelds. Ein Spielfeld ist durch den index eindeutig identifiziert.
5
+ # Das type Attribut gibt an, um welchen Feldtyp es sich handelt
5
6
  class Field
6
7
  # @!attribute [rw] type
7
- # @return [FieldType] the field's type
8
+ # @return [FieldType] der Typ des Feldes
8
9
  attr_accessor :type
9
- # @!attribute [r] x
10
- # @return [Integer] the field's x-coordinate
11
- attr_reader :x
12
- # @!attribute [r] y
13
- # @return [Integer] the field's y-coordinate
14
- attr_reader :y
15
-
16
- # @!attribute [r] direction
17
- # @return [Integer] the direction of the tile which the field belongs to
18
- attr_reader :direction
19
-
20
10
  # @!attribute [r] index
21
- # @return [Integer] the index of the tile which the field belongs to
11
+ # @return [Integer] der Index des Feldes (0 bis 64)
22
12
  attr_reader :index
23
13
 
24
- # @!attribute [r] points
25
- # @return [Integer] the points awarded to a player placed on this field additionally to points for current tile and passengers
26
- attr_reader :points
27
-
28
- # Initializer
14
+ # Konstruktor
29
15
  #
30
- # @param type [FieldType] field type
31
- # @param x [Integer] x-coordinate
32
- # @param y [Integer] y-coordinate
33
- # @param index [Integer] index of tile
34
- # @param direction [Integer] direction of tile
35
- # @param points [Integer] points
36
- def initialize(type, x, y, index, direction, points)
16
+ # @param type [FieldType] Feldtyp
17
+ # @param index [Integer] Index
18
+ def initialize(type, index)
37
19
  self.type = type
38
- @x = x
39
- @y = y
40
20
  @index = index
41
- @direction = direction
42
- @points = points
43
21
  end
44
22
 
45
23
  def ==(another_field)
46
24
  return self.type == another_field.type &&
47
- self.x == another_field.x &&
48
- self.y == another_field.y
49
- end
50
-
51
- # @return [Boolean] true if the field may never be moved onto.
52
- def blocked?
53
- [FieldType::BLOCKED,
54
- FieldType::PASSENGER0,
55
- FieldType::PASSENGER1,
56
- FieldType::PASSENGER2,
57
- FieldType::PASSENGER3,
58
- FieldType::PASSENGER4,
59
- FieldType::PASSENGER5].include? type
25
+ self.index == another_field.index
60
26
  end
61
27
 
62
28
  def to_s
63
- return "Field: x = #{self.x}, y = #{self.y}, type = #{self.type}"
29
+ return "Feld ##{self.index}, Typ = #{self.type}"
64
30
  end
65
31
  end
@@ -1,30 +1,24 @@
1
- # encoding: UTF-8
1
+ # encoding: utf-8
2
2
 
3
3
  require 'typesafe_enum'
4
- # All possible field types:
5
- # * WATER
6
- # * BLOCKED
7
- # * PASSENGER0
8
- # * PASSENGER1
9
- # * PASSENGER2
10
- # * PASSENGER3
11
- # * PASSENGER4
12
- # * PASSENGER5
13
- # * SANDBANK
14
- # * LOG
15
- # * GOAL
16
- # Access them with FieldType::WATER.
4
+ # Zahl- und Flaggenfelder Die veränderten Spielregeln sehen nur noch die
5
+ # Felder 1,2 vor. Die Positionsfelder 3 und 4 wurden in Möhrenfelder
6
+ # umgewandelt, und (1,5,6) sind jetzt Position-1-Felder.
17
7
  class FieldType < TypesafeEnum::Base
18
- new :WATER
19
- new :BLOCKED
20
- new :PASSENGER0
21
- new :PASSENGER1
22
- new :PASSENGER2
23
- new :PASSENGER3
24
- new :PASSENGER4
25
- new :PASSENGER5
26
- new :SANDBANK
27
- new :LOG
28
- new :GOAL
29
-
8
+ new :POSITION_1, '1'
9
+ new :POSITION_2, '2'
10
+ # Igelfeld
11
+ new :HEDGEHOG, 'I'
12
+ # Salatfeld
13
+ new :SALAD, 'S'
14
+ # Karottenfeld
15
+ new :CARROT, 'C'
16
+ # Hasenfeld
17
+ new :HARE, 'H'
18
+ # außerhalb des Spielfeldes
19
+ new :INVALID, 'X'
20
+ # Zielfeld
21
+ new :GOAL, 'G'
22
+ # Startfeld
23
+ new :START, '0'
30
24
  end
@@ -0,0 +1,378 @@
1
+ # coding: utf-8
2
+
3
+ require_relative 'field_type'
4
+ require_relative 'card_type'
5
+
6
+ # All methods which define the game rules. Needed for checking validity of moves
7
+ # and performing them.
8
+ class GameRules
9
+
10
+ # Berechnet wie viele Karotten für einen Zug der länge
11
+ # <code>moveCount</code> benötigt werden.
12
+ #
13
+ # @param moveCount Anzahl der Felder, um die bewegt wird
14
+ # @return Anzahl der benötigten Karotten
15
+ def self.calculate_carrots(moveCount)
16
+ (moveCount * (moveCount + 1)) / 2
17
+ end
18
+
19
+ # Berechnet, wieviele Züge mit <code>carrots</code> Karotten möglich sind.
20
+ #
21
+ # @param carrots maximal ausgegebene Karotten
22
+ # @return Felder um die maximal bewegt werden kann
23
+ def self.calculate_movable_fields(carrots)
24
+ # bei 30 Runden koennen nur 990 Karotten gesammelt werden
25
+ return 44 if carrots >= 990
26
+ return 0 if carrots < 1
27
+ (Math.sqrt(2 * carrots + 1/4) - 1/2).round
28
+ end
29
+
30
+ # Überprüft <code>Advance</code> Aktionen auf ihre Korrektheit. Folgende
31
+ # Spielregeln werden beachtet:
32
+ #
33
+ # - Der Spieler muss genügend Karotten für den Zug besitzen
34
+ # - Wenn das Ziel erreicht wird, darf der Spieler nach dem Zug maximal 10 Karotten übrig haben
35
+ # - Man darf nicht auf Igelfelder ziehen
36
+ # - Salatfelder dürfen nur betreten werden, wenn man noch Salate essen muss
37
+ # - Hasenfelder dürfen nur betreten werden, wenn man noch Hasenkarten ausspielen kann
38
+ #
39
+ # @param state GameState
40
+ # @param distance relativer Abstand zur aktuellen Position des Spielers
41
+ # @return [true, ''] falls ein Vorwärtszug möglich ist, [false, M] falls nicht, wobei M ein String mit einer Begruendung ist
42
+ def self.is_valid_to_advance(state, distance)
43
+ return false, 'Ein Vorwärtszug benötigt eine Mindestdistanz von einem Feld.' if distance <= 0
44
+ player = state.current_player
45
+ return false, 'Es muss ein Salat gegessen werden, bevor ein Vorwärtszug gemacht werden kann.' if must_eat_salad(state)
46
+ required_carrots = GameRules.calculate_carrots(distance)
47
+ return false, "Nicht genug Karotten, um #{distance} Felder vorwärts zu ziehen (Vorrat: #{player.carrots}, benötigt: #{required_carrots}" if (required_carrots > player.carrots)
48
+ new_position = player.index + distance
49
+ return false, 'Zielfeld wird von anderem Spieler besetzt.' if state.occupied_by_other_player?(state.field(new_position))
50
+ case state.field(new_position).type
51
+ when FieldType::INVALID
52
+ return false, "Zielfeld #{new_position} ist nicht vorhanden."
53
+ when FieldType::SALAD
54
+ return false, 'Ohne Salat darf ein Salatfeld nicht betreten werden.' if player.salads < 1
55
+ when FieldType::HARE
56
+ state2 = state.deep_clone
57
+ state2.set_last_action(Advance.new(distance))
58
+ state2.current_player.index = new_position
59
+ state2.current_player.carrots -= required_carrots
60
+ return false, 'Auf ein Hasenfeld darf nur gezogen werden, wenn eine Karte gespielt werden kann' unless can_play_any_card(state2)
61
+ when FieldType::GOAL
62
+ carrotsLeft = player.carrots - required_carrots
63
+ return false, "Auf das Zielfeld darf nur mit maximal 10 Karotten gezogen werden (es sind aber #{carrotsLeft} bei Erreichen des Zielfeldes)." unless carrotsLeft <= 10
64
+ return false, "Auf das Zielfeld darf nur ohne Salate gezogen werden (es sind aber #{player.salads} übrig)." unless player.salads == 0
65
+ when FieldType::HEDGEHOG
66
+ return false, 'Auf ein Igelfeld darf nicht vorwärts gezogen werden.'
67
+ when FieldType::CARROT, FieldType::POSITION_1, FieldType::START, FieldType::POSITION_2
68
+ return true, ''
69
+ else
70
+ raise "Unknown Type #{state.field(new_position).type.inspect}"
71
+ end
72
+ return true, ''
73
+ end
74
+
75
+ # Überprüft, ob ein Spieler aussetzen darf. Er darf dies, wenn kein anderer Zug möglich ist.
76
+ # @param state GameState
77
+ # @return true, falls der derzeitige Spieler keine andere Aktion machen kann.
78
+ def self.is_valid_to_skip(state)
79
+ return !GameRules.can_do_anything(state)
80
+ end
81
+
82
+ # Überprüft, ob ein Spieler einen Zug (keinen Aussetzug)
83
+ # @param state GameState
84
+ # @return true, falls ein Zug möglich ist.
85
+ def self.can_do_anything(state)
86
+ return can_play_any_card(state) || is_valid_to_fall_back(state)[0] ||
87
+ is_valid_to_exchange_carrots(state, 10)[0] ||
88
+ is_valid_to_exchange_carrots(state, -10)[0] ||
89
+ is_valid_to_eat(state)[0] || can_advance_to_any_field(state)
90
+ end
91
+
92
+ # Überprüft ob der derzeitige Spieler zu irgendeinem Feld einen Vorwärtszug machen kann.
93
+ # @param state GameState
94
+ # @return true, falls der Spieler irgendeinen Vorwärtszug machen kann
95
+ def self.can_advance_to_any_field(state)
96
+ fields = calculate_movable_fields(state.getCurrentPlayer().getCarrots())
97
+ (0..fields).to_a.any? do |i|
98
+ is_valid_to_advance(state, i)[0]
99
+ end
100
+ end
101
+
102
+ # Überprüft <code>EatSalad</code> Züge auf Korrektheit. Um einen Salat
103
+ # zu verzehren muss der Spieler sich:
104
+ #
105
+ # - auf einem Salatfeld befinden
106
+ # - noch mindestens einen Salat besitzen
107
+ # - vorher kein Salat auf diesem Feld verzehrt wurde
108
+ #
109
+ # @param state GameState
110
+ # @return [true, ''], falls ein Salad gegessen werden darf, [false, M] falls nicht, wobei M ein String mit dem Grund ist
111
+ def self.is_valid_to_eat(state)
112
+ return false, 'Salate dürfen nur auf Salatfeldern gegessen werden.' unless state.current_field.type == FieldType::SALAD
113
+ return false, 'Spieler hat keine Salate mehr zum Essen.' if state.current_player.salads < 1
114
+ return false, 'Spieler muss das Feld verlassen.' if player_must_advance(state)
115
+ return true, ''
116
+ end
117
+
118
+ # Überprüft ab der derzeitige Spieler im nächsten Zug einen Vorwärtszug machen muss.
119
+ # @param state GameState
120
+ # @return true, falls der derzeitige Spieler einen Vorwärtszug gemacht werden muss
121
+ def self.player_must_advance(state)
122
+ player = state.current_player
123
+ type = state.board.field(player.index).type
124
+
125
+ return true if (type == FieldType::HEDGEHOG || type == FieldType::START)
126
+
127
+ last_action = state.current_player.last_non_skip_action
128
+
129
+ if (!last_action.nil?)
130
+ if (last_action.instance_of? EatSalad)
131
+ return true
132
+ elsif (last_action.instance_of? Card)
133
+ # the player has to leave a rabbit field in next turn
134
+ if (last_action.type == CardType::EAT_SALAD)
135
+ return true
136
+ elsif (last_action.type == CardType::TAKE_OR_DROP_CARROTS) # the player has to leave the rabbit field
137
+ return true
138
+ end
139
+ end
140
+ end
141
+
142
+ return false
143
+ end
144
+
145
+ # Überprüft ob der derzeitige Spieler 10 Karotten nehmen oder abgeben kann.
146
+ # @param state GameState
147
+ # @param n 10 oder -10 je nach Fragestellung
148
+ # @return [true, ''], falls die durch n spezifizierte Aktion möglich ist, [false, M] falls nicht, wobei M ein String mit dem Grund ist
149
+ def self.is_valid_to_exchange_carrots(state, n)
150
+ player = state.current_player
151
+ return false, "Karotten können nur auf einem Karottenfeld #{n > 0 ? 'genommen' : 'abgegeben'} werden" if state.board.field(player.index).type != FieldType::CARROT
152
+ return true, '' if n == 10
153
+ return false, 'Gültige Karottenzahlen sind 10 und -10.' unless n == -10
154
+ return false, "Spieler hat keine 10 Karotten zum Abgeben (er hat #{player.carrots})." if player.carrots < 10
155
+ return true, ''
156
+ end
157
+
158
+ # Überprüft <code>FallBack</code> Züge auf Korrektheit
159
+ #
160
+ # @param state GameState
161
+ # @return [true, ''], falls der currentPlayer einen Rückzug machen darf, [false, M] falls nicht, wobei M ein String mit dem Grund ist.
162
+ def self.is_valid_to_fall_back(state)
163
+ return false, 'Spieler muss einen Salat fressen.' if must_eat_salad(state)
164
+ target_field = state.previous_field_of_type(
165
+ FieldType::HEDGEHOG, state.current_player.index
166
+ )
167
+ return false, 'Es gibt kein Igelfeld hinter dem Spieler.' if target_field.nil?
168
+ return false, 'Das Igelfeld hinter dem Spieler ist besetzt.' if state.occupied_by_other_player?(target_field)
169
+ return true, ''
170
+ end
171
+
172
+ # Überprüft ob der derzeitige Spieler die <code>FALL_BACK</code> Karte spielen darf.
173
+ # @param state GameState
174
+ # @return [true, ''], falls die <code>FALL_BACK</code> Karte gespielt werden darf, [false, M] falls nicht, wobei M ein String mit dem Grund ist.
175
+ def self.is_valid_to_play_fall_back(state)
176
+ player = state.current_player
177
+ return false, 'Spieler muss einen Vorwärtszug machen.' if player_must_advance(state)
178
+ return false, 'Karten können nur auf Hasenfeldern gespielt werden.' unless state.current_field.type == FieldType::HARE
179
+ return false, 'Nur der erste Spieler darf die FALL_BACK Karte spielen.' unless state.is_first(player)
180
+ return false, 'Spieler besitzt die Karte FALL_BACK nicht.' unless player.owns_card_of_type(CardType::FALL_BACK)
181
+
182
+ next_pos = state.other_player.index - 1
183
+
184
+ case state.field(next_pos).type
185
+ when FieldType::INVALID
186
+ return false, 'Durch Spielen der FALL_BACK Karte darf man nicht auf einem nicht vorhandenen Feld landen (also vor dem Start).'
187
+ when FieldType::HEDGEHOG
188
+ return false, 'Durch Spielen der FALL_BACK Karte darf man nicht auf einem Igelfeld landen.'
189
+ when FieldType::SALAD
190
+ return false, 'Spieler käme durch Spielen der FALL_BACK Kart auf ein Salatfeld, hat aber keine Salate.' if player.salads < 1
191
+ when FieldType::HARE
192
+ state2 = state.deep_clone
193
+ state2.set_last_action(Card.new(CardType::HURRY_AHEAD))
194
+ state2.current_player.cards.delete(CardType::FALL_BACK)
195
+ 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)
196
+ when FieldType::START
197
+ when FieldType::CARROT
198
+ when FieldType::POSITION_1
199
+ when FieldType::POSITION_2
200
+ return true, ''
201
+ when FieldType::GOAL
202
+ raise 'Player got onto goal by playing a fall back card. This should never happen.'
203
+ else
204
+ raise "Unknown Type #{state.field(next_pos).type.inspect}"
205
+ end
206
+ return true, ''
207
+ end
208
+
209
+ # Überprüft ob der derzeitige Spieler die <code>HURRY_AHEAD</code> Karte spielen darf.
210
+ # @param state GameState
211
+ # @return [true, ''], falls die <code>HURRY_AHEAD</code> Karte gespielt werden darf, [false, M], falls nicht, wobei M ein String mit dem Grund ist.
212
+ def self.is_valid_to_play_hurry_ahead(state)
213
+ player = state.current_player
214
+ return false, 'Spieler muss einen Vorwärtszug machen.' if player_must_advance(state)
215
+ return false, 'Karten können nur auf Hasenfeldern gespielt werden.' unless state.current_field.type == FieldType::HARE
216
+ return false, 'Nur der zweite Spieler darf die HURRY_AHEAD Karte spielen.' unless state.is_second(player)
217
+ return false, 'Spieler besitzt die Karte HURRY_AHEAD nicht.' unless player.owns_card_of_type(CardType::HURRY_AHEAD)
218
+
219
+ o = state.other_player
220
+ next_pos = o.index + 1
221
+
222
+ case state.field(next_pos).type
223
+ when FieldType::INVALID
224
+ return false, 'Durch Spielen der HURRY_AHEAD Karte darf man nicht auf einem nicht vorhandenen Feld landen (also nach dem Ziel).'
225
+ when FieldType::HEDGEHOG
226
+ return false, 'Durch Spielen der HURRY_AHEAD Karte darf man nicht auf einem Igelfeld landen.'
227
+ when FieldType::SALAD
228
+ return false, 'Spieler käme durch Spielen der HURRY_AHEAD Kart auf ein Salatfeld, hat aber keine Salate.' if player.salads < 1
229
+ when FieldType::HARE
230
+ state2 = state.deep_clone
231
+ state2.set_last_action(Card.new(CardType::HURRY_AHEAD))
232
+ state2.current_player.cards.delete(CardType::HURRY_AHEAD)
233
+ 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)
234
+ when FieldType::GOAL
235
+ 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)
236
+ when FieldType::CARROT
237
+ when FieldType::POSITION_1
238
+ when FieldType::POSITION_2
239
+ return true, ''
240
+ when FieldType::START
241
+ raise 'Player got onto start field by playing a hurry ahead card. This should never happen.'
242
+ else
243
+ raise "Unknown Type #{state.field(next_pos).type.inspect}"
244
+ end
245
+ return true, ''
246
+ end
247
+
248
+ # Überprüft ob der derzeitige Spieler die <code>TAKE_OR_DROP_CARROTS</code> Karte spielen darf.
249
+ # @param state GameState
250
+ # @param n 20 für nehmen, -20 für abgeben, 0 für nichts tun
251
+ # @return [true, ''], falls die <code>TAKE_OR_DROP_CARROTS</code> Karte gespielt werden darf, [false, M], falls nicht, wobei M ein String mit dem Grund ist.
252
+ def self.is_valid_to_play_take_or_drop_carrots(state, n)
253
+ player = state.current_player
254
+ return false, 'Spieler muss einen Vorwärtszug machen.' if player_must_advance(state)
255
+ return false, 'Karten können nur auf Hasenfeldern gespielt werden.' unless state.current_field.type == FieldType::HARE
256
+ return false, 'Spieler besitzt die Karte TAKE_OR_DROP_CARROTS nicht.' unless player.owns_card_of_type(CardType::TAKE_OR_DROP_CARROTS)
257
+ 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)
258
+ return true, '' if n >= 0
259
+ # at this point, n has to be -20
260
+ return false, "Spieler hat keine 20 Karotten zum Abgeben (er hat #{player.carrots})." if player.carrots < 20
261
+ return true, ''
262
+ end
263
+
264
+ # Überprüft ob der derzeitige Spieler die <code>EAT_SALAD</code> Karte spielen darf.
265
+ # @param state GameState
266
+ # @return (true, ''), falls die <code>EAT_SALAD</code> Karte gespielt werden darf, (false, M) falls nicht, wobei M ein String mit dem Grund ist
267
+ def self.is_valid_to_play_eat_salad(state)
268
+ player = state.current_player
269
+ return false, 'Es muss vorwärts gezogen werden.' if player_must_advance(state)
270
+ return false, 'Karten können nur auf Hasenfeldern gespielt werden.' unless state.current_field.type == FieldType::HARE
271
+ return false, 'Spieler besitzt die Karte nicht.' unless player.owns_card_of_type(CardType::EAT_SALAD)
272
+ return false, 'Spieler hat keine Salate zum Essen' if player.salads < 1
273
+ return true, ''
274
+ end
275
+
276
+ # Überprüft ob der derzeitige Spieler irgendeine Karte spielen kann.
277
+ # TAKE_OR_DROP_CARROTS wird nur mit 20 überprüft
278
+ # @param state GameState
279
+ # @return true, falls das Spielen einer Karte möglich ist
280
+ def self.can_play_any_card(state)
281
+ valid = false
282
+ player = state.current_player
283
+
284
+ player.cards.any? do |card|
285
+ case card
286
+ when CardType::EAT_SALAD
287
+ is_valid_to_play_eat_salad(state)[0]
288
+ when CardType::FALL_BACK
289
+ is_valid_to_play_fall_back(state)[0]
290
+ when CardType::HURRY_AHEAD
291
+ is_valid_to_play_hurry_ahead(state)[0]
292
+ when CardType::TAKE_OR_DROP_CARROTS
293
+ is_valid_to_play_take_or_drop_carrots(state, 20)[0]
294
+ else
295
+ raise "Unknown CardType " + card
296
+ end
297
+ end
298
+ end
299
+
300
+ # Überprüft ob der derzeitige Spieler die Karte spielen kann.
301
+ # @param state
302
+ # @param c Karte die gespielt werden soll
303
+ # @param n Parameter mit dem TAKE_OR_DROP_CARROTS überprüft wird, default 0
304
+ # @return true, falls das Spielen der entsprechenden karte möglich ist
305
+ def self.is_valid_to_play_card(state, c, n = 0)
306
+ case c
307
+ when CardType::EAT_SALAD
308
+ isValidToPlayEatSalad(state)[0]
309
+ when CardType::FALL_BACK
310
+ is_valid_to_play_fall_back(state)[0]
311
+ when CardType::HURRY_AHEAD
312
+ is_valid_to_play_hurry_ahead(state)[0]
313
+ when CardType::TAKE_OR_DROP_CARROTS
314
+ is_valid_to_play_take_or_drop_carrots(state, n)[0]
315
+ else
316
+ raise "Unknown CardType " + c
317
+ end
318
+ end
319
+
320
+ def self.must_eat_salad(state)
321
+ player = state.current_player
322
+ # check whether player just moved to salad field and must eat salad
323
+ field = state.board.field(player.index)
324
+ if field.type == FieldType::SALAD
325
+ if player.last_non_skip_action&.instance_of?(Advance)
326
+ return true
327
+ elsif player.last_non_skip_action&.instance_of?(Card)
328
+ card_action = player.last_non_skip_action
329
+ if card_action.card_type == CardType::FALL_BACK ||
330
+ card_action.card_type == CardType::HURRY_AHEAD
331
+ return true
332
+ end
333
+ end
334
+ end
335
+ return false
336
+ end
337
+
338
+ # TODO difference isValidToPlayCard
339
+ # @param state
340
+ # @return
341
+ def self.can_play_card(state)
342
+ player = state.getCurrentPlayer()
343
+ canPlayCard = state.getTypeAt(player.getFieldIndex()).equals(FieldType.HARE)
344
+ player.getCards().each do |card|
345
+ canPlayCard = canPlayCard || is_valid_to_play_card(state, card, 0)
346
+ end
347
+ return canPlayCard
348
+ end
349
+
350
+ # TODO difference isVAlidTOMove
351
+ # @param state
352
+ # @return
353
+ def self.can_move(state)
354
+ can_move = false
355
+ max_distance = GameRules.calculate_movable_fields(state.getCurrentPlayer().getCarrots())
356
+ (1..max_distance).to_a.each do |i|
357
+ can_move = can_move || isValidToAdvance(state, i)
358
+ end
359
+ return can_move
360
+ end
361
+
362
+ # Überprüft ob eine Karte gespielt werden muss. Sollte nach einem
363
+ # Zug eines Spielers immer false sein, ansonsten ist Zug ungültig.
364
+ # @param state derzeitiger GameState
365
+ def self.must_play_card(state)
366
+ state.current_player.must_play_card
367
+ end
368
+
369
+
370
+ # Überprüft ob ein der derzeitige Spieler das Ziel betreten darf
371
+ # @param state GameState
372
+ # @return
373
+ def self.can_enter_goal(state)
374
+ player = state.current_player
375
+ player.carrots <= 10 && player.salads == 0
376
+ end
377
+
378
+ end