software_challenge_client 19.1.0 → 20.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,9 +1,8 @@
1
1
  # coding: utf-8
2
2
  # frozen_string_literal: true
3
3
 
4
- require_relative 'field_type'
5
- require_relative 'line'
6
4
  require_relative './util/constants'
5
+ require_relative 'invalid_move_exception'
7
6
 
8
7
  # Methoden, welche die Spielregeln von Piranhas abbilden.
9
8
  #
@@ -12,227 +11,385 @@ class GameRuleLogic
12
11
 
13
12
  include Constants
14
13
 
15
- # Fügt einem leeren Spielfeld zwei Krakenfelder hinzu. Die beiden Felder
16
- # liegen nicht auf derselben Horizontalen, Vertikalen oder Diagonalen und sind
17
- # mindestens zwei Felder von den Rändern des Spielbrettes entfernt.
14
+ # Fügt einem leeren Spielfeld drei Brombeeren hinzu.
18
15
  #
19
16
  # Diese Methode ist dazu gedacht, ein initiales Spielbrett regelkonform zu generieren.
20
17
  #
21
18
  # @param board [Board] Das zu modifizierende Spielbrett. Es wird nicht
22
- # geprüft, ob sich auf dem Spielbrett bereits Krakenfelder befinden.
19
+ # geprüft, ob sich auf dem Spielbrett bereits Brombeeren befinden.
23
20
  # @return [Board] Das modifizierte Spielbrett.
24
21
  def self.add_blocked_fields(board)
25
- number_of_blocked_fields = 2
26
- lower_bound = 2 # first row or column, in which blocked fields are allowed
27
- upper_bound = 7 # last row or column, in which blocked fields are allowed
28
-
29
- # create a list of coordinates for fields which may be blocked
30
- blockable_field_coordinates = (lower_bound..upper_bound).to_a.map do |x|
31
- (lower_bound..upper_bound).to_a.map do |y|
32
- Coordinate.new(x, y)
33
- end
34
- end.flatten
35
-
36
- # set fields with randomly selected coordinates to blocked coordinates may
37
- # not lay on same horizontal, vertical or diagonal lines with other selected
38
- # coordinates
39
- number_of_blocked_fields.times do
40
- selected_coords = blockable_field_coordinates.sample
41
- board.change_field(selectedCoords, FieldType::OBSTRUCTED)
42
- # remove field coordinates and fields on horizontal, vertical and diagonal
43
- # lines:
44
- coordinates_to_remove = ALL_DIRECTIONS.map do |direction|
45
- Line.new(selected_coords, direction).to_a
46
- end.flatten
47
- blockable_field_coordinates = blockable_field_coordinates.filter do |c|
48
- coordinates_to_remove.none? do |to_remove|
49
- c.x == to_remove.x && c.y == to_remove.y
22
+ raise "todo"
23
+ board
24
+ end
25
+
26
+ def self.get_neighbour_in_direction(board, coords, direction)
27
+ board.field_at(direction.translate(coords))
28
+ end
29
+
30
+ def self.get_neighbours(board, coordinates)
31
+ Direction.map { |d| get_neighbour_in_direction(board, coordinates, d) }.compact
32
+ end
33
+
34
+ def self.is_bee_blocked(board, color)
35
+ bee_fields = board.field_list.select { |f| f.pieces.include?(Piece.new(color, PieceType::BEE)) }
36
+ return false if bee_fields.empty?
37
+ return get_neighbours(board, bee_fields[0].coordinates).all? { |f| !f.empty? }
38
+ end
39
+
40
+ # Prueft, ob ein Spielzug fuer den gegebenen Gamestate valide ist
41
+ #
42
+ # @param gamestate [Gamestate]
43
+ # @param move [Move]
44
+ # @return [?]
45
+ def self.valid_move?(gamestate, move)
46
+ case move
47
+ when SetMove
48
+ validate_set_move(gamestate, move)
49
+ when DragMove
50
+ validate_drag_move(gamestate, move)
51
+ when SkipMove
52
+ validate_skip_move(gamestate, move)
53
+ end
54
+ end
55
+
56
+ def self.is_on_board(coords)
57
+ shift = (BOARD_SIZE - 1) / 2
58
+ -shift <= coords.x && coords.x <= shift && -shift <= coords.y && coords.y <= shift
59
+ end
60
+
61
+ def self.has_player_placed_bee(gamestate)
62
+ gamestate.deployed_pieces(gamestate.current_player_color).any? { |p| p.type == PieceType::BEE }
63
+ end
64
+
65
+ def self.validate_set_move(gamestate, move)
66
+ unless is_on_board(move.destination)
67
+ raise InvalidMoveException.new("Piece has to be placed on board. Destination ${move.destination} is out of bounds.", move)
68
+ end
69
+ unless gamestate.board.field_at(move.destination).empty?
70
+ raise InvalidMoveException.new("Set destination is not empty!", move)
71
+ end
72
+
73
+ owned_fields = gamestate.board.fields_of_color(gamestate.current_player_color)
74
+ if owned_fields.empty?
75
+ other_player_fields = gamestate.board.fields_of_color(gamestate.other_player_color)
76
+ if !other_player_fields.empty?
77
+ unless other_player_fields.map{ |of| get_neighbours(gamestate.board, of.coordinates).map{ |n| n.coordinates } }.flatten.include?(move.destination)
78
+ raise InvalidMoveException.new("Piece has to be placed next to other players piece", move)
50
79
  end
51
80
  end
81
+ else
82
+ if gamestate.round == 3 && !has_player_placed_bee(gamestate) && move.piece.type != PieceType::BEE
83
+ raise InvalidMoveException.new("The bee must be placed in fourth round latest", move)
84
+ end
85
+
86
+ if !gamestate.undeployed_pieces(gamestate.current_player_color).include?(move.piece)
87
+ raise InvalidMoveException.new("Piece is not a undeployed piece of the current player", move)
88
+ end
89
+
90
+ destination_neighbours = get_neighbours(gamestate.board, move.destination)
91
+ if !destination_neighbours.any? { |f| f.color == gamestate.current_player_color }
92
+ raise InvalidMoveException.new("A newly placed piece must touch an own piece", move)
93
+ end
94
+ if destination_neighbours.any? { |f| f.color == gamestate.other_player_color }
95
+ raise InvalidMoveException.new("A newly placed is not allowed to touch an opponent's piece", move)
96
+ end
52
97
  end
53
- board
98
+ true
54
99
  end
55
100
 
56
- # Ermittlung der Anzahl der Fische auf einer Line des Spielbrettes.
57
- #
58
- # @param board [Board] Das zu betrachtende Spielbrett.
59
- # @param start [Coordinates] Ein Feld auf der Linie.
60
- # @param direction [LineDirection] Die Ausrichtung der Linie (vertikal, horizontal oder diagonal).
61
- # @return [Integer] Anzahl der Fische auf der Linie.
62
- def self.count_fish(board, start, direction)
63
- # filter function for fish field type
64
- fish = proc { |f| f.type == FieldType::RED || f.type == FieldType::BLUE }
65
- Line.new(start, direction).to_a.map do |p|
66
- board.field(p.x, p.y)
67
- end.select(&fish).size
68
- end
69
-
70
- # @return [Coordinates] Die Zielkoordinaten eines Spielzuges auf einem Spielbrett.
71
- def self.target_coordinates(move, board)
72
- speed = GameRuleLogic.count_fish(
73
- board, move.from_field,
74
- Line.line_direction_for_direction(move.direction)
75
- )
76
- move.target_field(speed)
77
- end
78
-
79
- # @return [Field] Das Zielfeld eines Spielzuges auf einem Spielbrett.
80
- def self.move_target(move, board)
81
- c = GameRuleLogic.target_coordinates(move, board)
82
- board.field(c.x, c.y)
83
- end
84
-
85
- # Prüft, ob sich die gegebenen Koordinaten innerhalb des Spielbrettes befinden.
86
- # @return [Boolean]
87
- def self.inside_bounds?(coordinates)
88
- coordinates.x >= 0 &&
89
- coordinates.x < SIZE &&
90
- coordinates.y >= 0 &&
91
- coordinates.y < SIZE
92
- end
93
-
94
- # Ermittelt, ob der gegebene Feldtyp für den Spieler mit der angegebenen Farbe ein nicht überspringbares Hindernis darstellt.
95
- # @param field_type [FieldType]
96
- # @param moving_player_color [PlayerColor]
97
- # @return [Boolean] true, falls es ein Hindernis ist, false sonst.
98
- def self.obstacle?(field_type, moving_player_color)
99
- field_type == PlayerColor.field_type(
100
- PlayerColor.opponent_color(moving_player_color)
101
- )
102
- end
103
-
104
- # Ermittelt, ob sich zwischen den angegebenen Feldern kein Hindernis befindet.
105
- # @param from_field [Coordinates] Startfeld
106
- # @param to_field [Coordinates] Zielfeld
107
- # @param direction [LineDirection] Ausrichtung der Linie zwischen Start- und Zielfeld.
108
- # @param color [PlayerColor] Farbe des ziehenden Spielers.
109
- # @param board [Board] Das aktuelle Spielbrett.
110
- # @return [Boolean] true, falls der Spieler mit der angegebenen Farbe zwischen den beiden Punkten ein Hindernis vorfindet, false sonst.
111
- def self.obstacle_between?(from_field, direction, to_field, color, board)
112
- Line.new(from_field, direction)
113
- .to_a
114
- .select { |c| Line.between(from_field, to_field, direction).call(c) }
115
- .any? { |f| GameRuleLogic.obstacle?(board.field(f.x, f.y).type, color) }
116
- end
117
-
118
- # Ermittelt, ob der Spieler mit der angegebenen Farbe einen Fisch auf dem Feld mit den angegebenen Koordinaten besitzt.
119
- # @param target [Coordinates] Koordinaten des Feldes.
120
- # @param moving_player_color [PlayerColor] Farbe des Spielers, der einen Zug machen will.
121
- # @param board [Board] Aktuelles Spielbrett.
122
- # @return [Boolean] true falls sich auf dem Feld ein Fisch mit der richtigen Farbe befindet (Rot für roten Spieler, Blau für blauen Spieler), false sonst.
123
- def self.valid_move_target(target, moving_player_color, board)
124
- target_field_type = board.field(target.x, target.y).type
125
- target_field_type == FieldType::EMPTY ||
126
- target_field_type == PlayerColor.field_type(
127
- PlayerColor.opponent_color(moving_player_color)
128
- )
101
+ def self.validate_skip_move(gamestate, move)
102
+ if !possible_moves(gamestate).empty?
103
+ raise InvalidMoveException.new("Skipping a turn is only allowed when no other moves can be made.", move)
104
+ end
105
+ if gamestate.round == 3 && !has_player_placed_bee(gamestate)
106
+ raise InvalidMoveException.new("The bee must be placed in fourth round latest", move)
107
+ end
108
+ true
129
109
  end
130
110
 
131
- # Ermittelt, ob der gegebene Zug regelkonform ausgeführt werden kann.
132
- # @param move [Move] Der zu prüfende Zug
133
- # @param board [Board] Spielbrett, auf dem der Zug ausgeführt werden soll.
134
- # @param current_player_color [PlayerColor] Farbe des Spielers, der den Zug ausführen soll.
135
- # @return [Boolean] true falls der Zug gültig ist, false sonst.
136
- def self.valid_move?(move, board, current_player_color)
137
- from_field_type = board.field(move.x, move.y).type
138
- return false unless
139
- [FieldType::BLUE, FieldType::RED].include? from_field_type
140
- return false unless
141
- current_player_color == FieldType.player_color(from_field_type)
142
-
143
- return false unless
144
- GameRuleLogic.inside_bounds?(
145
- GameRuleLogic.target_coordinates(move, board)
146
- )
111
+ def self.validate_drag_move(gamestate, move)
112
+ unless has_player_placed_bee(gamestate)
113
+ raise InvalidMoveException.new("You have to place the bee to be able to perform dragmoves", move)
114
+ end
147
115
 
148
- target = GameRuleLogic.move_target(move, board)
116
+ if (!is_on_board(move.destination) || !is_on_board(move.start))
117
+ raise InvalidMoveException.new("The Move is out of bounds", move)
118
+ end
149
119
 
150
- GameRuleLogic.valid_move_target(target, current_player_color, board) &&
151
- !GameRuleLogic.obstacle_between?(
152
- move.from_field,
153
- Line.line_direction_for_direction(move.direction),
154
- target, current_player_color, board
155
- )
120
+ if (gamestate.board.field_at(move.start).pieces.empty?)
121
+ raise InvalidMoveException.new("There is no piece to move", move)
122
+ end
123
+
124
+ piece_to_drag = gamestate.board.field_at(move.start).pieces.last
125
+
126
+ if (piece_to_drag.owner != gamestate.current_player_color)
127
+ raise InvalidMoveException.new("Trying to move piece of the other player", move)
128
+ end
129
+
130
+ if (move.start == move.destination)
131
+ raise InvalidMoveException.new("Destination and start are equal", move)
132
+ end
133
+
134
+ if (!gamestate.board.field_at(move.destination).pieces.empty? && piece_to_drag.type != PieceType::BEETLE)
135
+ raise InvalidMoveException.new("Only beetles are allowed to climb on other Pieces", move)
136
+ end
137
+
138
+ board_without_piece = gamestate.board.clone
139
+ board_without_piece.field_at(move.start).pieces.pop
140
+
141
+ if (!is_swarm_connected(board_without_piece))
142
+ raise InvalidMoveException.new("Moving piece would disconnect swarm", move)
143
+ end
144
+
145
+ case piece_to_drag.type
146
+ when PieceType::ANT
147
+ validate_ant_move(board_without_piece, move)
148
+ when PieceType::BEE
149
+ validate_bee_move(board_without_piece, move)
150
+ when PieceType::BEETLE
151
+ validate_beetle_move(board_without_piece, move)
152
+ when PieceType::GRASSHOPPER
153
+ validate_grasshopper_move(board_without_piece, move)
154
+ when PieceType::SPIDER
155
+ validate_spider_move(board_without_piece, move)
156
+ end
157
+ true
156
158
  end
157
159
 
158
- # Ermittelt alle möglichen Züge von einem bestimmten Feld aus.
159
- # @param board [Board] Aktuelles Spielbrett
160
- # @param field [Field] Das Feld, von dem die Züge ausgehen sollen.
161
- # @param current_player_color [PlayerColor] Farbe des Spielers, der den Zug macht.
162
- # @return [Array<Move>] Liste von möglichen Zügen.
163
- def self.possible_moves(board, field, current_player_color)
164
- Direction.map { |direction| Move.new(field.x, field.y, direction) }
165
- .select do |m|
166
- GameRuleLogic.valid_move?(m, board, current_player_color)
167
- end
168
- end
169
-
170
- # Ermittelt die Schwarmgröße eines Spielers auf dem Spielbrett.
171
- # @param board [Board] Das zu betrachtende Spielbrett.
172
- # @param player_color [PlayerColor] Farbe des Spielers, für den die Schwarmgröße ermittelt werden soll.
173
- # @return [Integer] Anzahl der Fische im größten Schwarm des Spielers.
174
- def self.swarm_size(board, player_color)
175
- GameRuleLogic.greatest_swarm_from_fields(
176
- board,
177
- board.fields_of_type(
178
- PlayerColor.field_type(player_color)
179
- ).to_set,
180
- Set.new
181
- ).size
182
- end
183
-
184
- # @return [Array<Field>] Alle direkten Nachbarfelder des gegebenen Feldes. Für Felder im Inneren des Spielbrettes gibt es acht Nachbarfelder. Für Randfelder vier oder drei Nachbarfelder.
185
- def self.neighbours(board, field)
186
- Direction
187
- .map { |d| d.translate(field.coordinates) }
188
- .select { |c| GameRuleLogic.inside_bounds?(c) }
189
- .map { |c| board.field_at(c) }
190
- end
191
-
192
- # Hilfsfunktion für {GameRuleLogic.swarm_size}.
193
- # Ermittelt die größte zusammenhängende Menge von Feldern aus einer gegebenen Menge von Feldern.
194
- # @param board [Board] Das zu betrachtende Spielbrett.
195
- # @param fields_to_check [Set<Field>] Menge der Felder, aus der die größte zusammenhängende Menge ermittelt werden soll.
196
- # @param current_biggest_swarm [Set<Field>] Aktuell größte zusammenhängende Feldmenge. Für rekursiven Aufruf.
197
- # @return [Set<Field>]
198
- def self.greatest_swarm_from_fields(board, fields_to_check, current_biggest_swarm = Set.new)
199
- # stop searching when the size of the current found biggest set is bigger
200
- # than the rest of the fields or if there are no more fields to check
201
- return current_biggest_swarm if current_biggest_swarm.size > fields_to_check.size || fields_to_check.empty?
202
-
203
- # start a new set of adjacent fields with the first field in fields_to_check
204
- current_swarm = Set.new
205
- field = fields_to_check.to_a.first
206
- fields_to_check.delete(field)
207
- current_swarm.add(field)
208
-
209
- # move all adjacent fields to the set
210
- loop do
211
- to_add = current_swarm
212
- .map { |f| GameRuleLogic.neighbours(board, f) }
213
- .flatten
214
- .select { |f| fields_to_check.include? f }
215
- break if to_add.empty?
216
- fields_to_check -= to_add
217
- current_swarm += to_add
218
- end
219
-
220
- # keep trying to find bigger sets
221
- if current_swarm.size > current_biggest_swarm.size
222
- GameRuleLogic.greatest_swarm_from_fields(
223
- board, fields_to_check, current_swarm
224
- )
225
- else
226
- GameRuleLogic.greatest_swarm_from_fields(
227
- board, fields_to_check, current_biggest_swarm
160
+ def self.validate_ant_move(board, move)
161
+ visited_fields = [move.start]
162
+ index = 0
163
+ while index < visited_fields.size
164
+ current_field = visited_fields[index]
165
+ new_fields = accessible_neighbours_except(board, current_field, move.start).reject { |f| visited_fields.include? f }
166
+ return true if new_fields.map(&:coordinates).include?(move.destination)
167
+ visited_fields += new_fields
168
+ index += 1
169
+ end
170
+ raise InvalidMoveException.new("No path found for ant move", move)
171
+ end
172
+
173
+ def self.is_swarm_connected(board)
174
+ board_fields = board.field_list.select{ |f| !f.pieces.empty? }
175
+ return true if board_fields.empty?
176
+ visited_fields = board_fields.take 1
177
+ total_pieces = board.pieces.size
178
+ index = 0
179
+ while index < visited_fields.size
180
+ current_field = visited_fields[index]
181
+ occupied_neighbours =
182
+ get_neighbours(board, current_field.coordinates)
183
+ .filter { |f| !f.pieces.empty? }
184
+ occupied_neighbours -= visited_fields
185
+ visited_fields += occupied_neighbours
186
+ return true if visited_fields.sum{ |f| f.pieces.size } == total_pieces
187
+ index += 1
188
+ end
189
+ false
190
+ end
191
+
192
+ def self.validate_beetle_move(board, move)
193
+ validate_destination_next_to_start(move)
194
+ if ((shared_neighbours_of_two_coords(board, move.start, move.destination) + [board.field_at(move.destination), board.field_at(move.start)]).all? { |f| f.pieces.empty? })
195
+ raise InvalidMoveException.new("Beetle has to move along swarm", move)
196
+ end
197
+ end
198
+
199
+ def self.validate_destination_next_to_start(move)
200
+ if (!is_neighbour(move.start, move.destination))
201
+ raise InvalidMoveException.new("Destination field is not next to start field", move)
202
+ end
203
+ end
204
+
205
+ def self.is_neighbour(start, destination)
206
+ Direction.map do |d|
207
+ d.translate(start)
208
+ end.include?(destination)
209
+ end
210
+
211
+ def self.shared_neighbours_of_two_coords(board, first_coords, second_coords)
212
+ get_neighbours(board, first_coords) & get_neighbours(board, second_coords)
213
+ end
214
+
215
+ def self.validate_bee_move(board, move)
216
+ validate_destination_next_to_start(move)
217
+ if (!can_move_between(board, move.start, move.destination))
218
+ raise InvalidMoveException.new("There is no path to your destination", move)
219
+ end
220
+ end
221
+
222
+ def self.can_move_between(board, coords1, coords2)
223
+ shared = shared_neighbours_of_two_coords(board, coords1, coords2)
224
+ (shared.size == 1 || shared.any? { |n| n.empty? && !n.obstructed }) && shared.any? { |n| !n.pieces.empty? }
225
+ end
226
+
227
+ def self.validate_grasshopper_move(board, move)
228
+ if (!two_fields_on_one_straight(move.start, move.destination))
229
+ raise InvalidMoveException.new("Grasshopper can only move straight lines", move)
230
+ end
231
+ if (is_neighbour(move.start, move.destination))
232
+ raise InvalidMoveException.new("Grasshopper has to jump over at least one piece", move)
233
+ end
234
+ if (get_line_between_coords(board, move.start, move.destination).any? { |f| f.empty? })
235
+ raise InvalidMoveException.new("Grasshopper can only jump over occupied fields, not empty ones", move)
236
+ end
237
+ end
238
+
239
+ def self.two_fields_on_one_straight(coords1, coords2)
240
+ return coords1.x == coords2.x || coords1.y == coords2.y || coords1.z == coords2.z
241
+ end
242
+
243
+ def self.get_line_between_coords(board, start, destination)
244
+ if (!two_fields_on_one_straight(start, destination))
245
+ raise InvalidMoveException.new("destination is not in line with start")
246
+ end
247
+
248
+ # TODO use Direction shift
249
+ dX = start.x - destination.x
250
+ dY = start.y - destination.y
251
+ dZ = start.z - destination.z
252
+ d = (dX == 0) ? dY.abs : dX.abs
253
+ (1..(d-1)).to_a.map do |i|
254
+ board.field_at(
255
+ CubeCoordinates.new(
256
+ destination.x + i * (dX <=> 0),
257
+ destination.y + i * (dY <=> 0),
258
+ destination.z + i * (dZ <=> 0)
259
+ )
228
260
  )
229
261
  end
230
262
  end
263
+ def self.accessible_neighbours_except(board, start, except)
264
+ get_neighbours(board, start).filter do |neighbour|
265
+ neighbour.empty? && can_move_between_except(board, start, neighbour, except) && neighbour.coordinates != except
266
+ end
267
+ end
268
+
269
+ def self.can_move_between_except(board, coords1, coords2, except)
270
+ shared = shared_neighbours_of_two_coords(board, coords1, coords2).reject do |f|
271
+ f.pieces.size == 1 && except == f.coordinates
272
+ end
273
+ (shared.size == 1 || shared.any? { |s| s.empty? && !s.obstructed }) && shared.any? { |s| !s.pieces.empty? }
274
+ end
275
+
276
+ def self.validate_spider_move(board, move)
277
+ found = get_accessible_neighbours(board, move.start).any? do |depth_one|
278
+ get_accessible_neighbours_except(board, depth_one, move.start).any? do |depth_two|
279
+ get_accessible_neighbours_except(board, depth_two, move.start).reject{ |f| f.coordinates == depth_one.coordinates }.any? { |f| move.destination == f.coordinates }
280
+ end
281
+ end
282
+ return true if (found)
283
+ raise InvalidMoveException.new("No path found for spider move", move)
284
+ end
285
+
286
+ def self.get_accessible_neighbours(board, start)
287
+ get_neighbours(board, start).filter do |neighbour|
288
+ neighbour.empty? && can_move_between(board, start, neighbour)
289
+ end
290
+ end
291
+
292
+ def self.get_accessible_neighbours_except(board, start, except)
293
+ get_neighbours(board, start).filter do |neighbour|
294
+ neighbour.empty? &&
295
+ can_move_between_except(board, start, neighbour, except) &&
296
+ neighbour.coordinates != except
297
+ end
298
+ end
299
+
300
+ def self.perform_move(gamestate, move)
301
+ raise "Invalid move!" unless valid_move?(gamestate, move)
302
+ case move
303
+ when SetMove
304
+ gamestate.undeployed_pieces(move.piece.color).remove(move.piece)
305
+ gamestate.board.field_at(move.destination).add_piece(move.piece)
306
+ when DragMove
307
+ piece_to_move = gamestate.board.field_at(move.start).remove_piece
308
+ gamestate.board.field_at(move.destination).add_piece(piece_to_move)
309
+ end
310
+ gamestate.turn += 1
311
+ gamestate.last_move = move
312
+ end
313
+
314
+ # all possible moves, but will *not* return the skip move if no other moves are possible!
315
+ def self.possible_moves(gamestate)
316
+ possible_set_moves(gamestate) + possible_drag_moves(gamestate)
317
+ end
318
+
319
+ def self.possible_drag_moves(gamestate)
320
+ gamestate.board.fields_of_color(gamestate.current_player_color).flat_map do |start_field|
321
+ edge_targets = empty_fields_connected_to_swarm(gamestate.board)
322
+ additional_targets =
323
+ if start_field.pieces.last.type == PieceType::BEETLE
324
+ get_neighbours(gamestate.board, start_field).uniq
325
+ else
326
+ []
327
+ end
328
+ edge_targets + additional_targets.map do |destination|
329
+ move = DragMove.new(start_field, destination)
330
+ begin
331
+ valid_move?(gamestate, move)
332
+ move
333
+ rescue InvalidMoveException
334
+ nil
335
+ end
336
+ end.compact
337
+ end
338
+ end
339
+
340
+ def self.empty_fields_connected_to_swarm(board)
341
+ board.field_list
342
+ .filter { |f| f.has_owner }
343
+ .flat_map { |f| get_neighbours(board, f).filter { f.empty? } }
344
+ .uniq
345
+ end
346
+
347
+ def self.possible_set_move_destinations(board, owner)
348
+ board.fields_of_color(owner)
349
+ .flat_map { |f| get_neighbours(board, f).filter { |f| f.empty? } }
350
+ .uniq
351
+ .filter { |f| get_neighbours(board, f).all? { |n| n.color != owner.opponent } }
352
+ end
353
+
354
+ def self.possible_set_moves(gamestate)
355
+ undeployed = gamestate.undeployed_pieces(gamestate.current_player_color)
356
+ set_destinations =
357
+ if (undeployed.size == STARTING_PIECES.size)
358
+ # current player has not placed any pieces yet (first or second turn)
359
+ if (gamestate.undeployed_pieces(gamestate.other_player_color).size == STARTING_PIECES.size)
360
+ # other player also has not placed any pieces yet (first turn, all destinations allowed (except obstructed)
361
+ gamestate.board.field_list.filter { |f| f.empty? }
362
+ else
363
+ # other player placed a piece already
364
+ gamestate.board
365
+ .fields_of_color(gamestate.other_player_color)
366
+ .flat_map do |f|
367
+ GameRuleLogic.get_neighbours(gamestate.board, f).filter(&:empty?)
368
+ end
369
+ end
370
+ else
371
+ possible_set_move_destinations(gamestate.board, gamestate.current_player_color)
372
+ end
373
+
374
+ possible_piece_types =
375
+ if (!has_player_placed_bee(gamestate) && gamestate.turn > 5)
376
+ [PieceType::BEE]
377
+ else
378
+ undeployed.map(&:type).uniq
379
+ end
380
+ set_destinations
381
+ .flat_map do |d|
382
+ possible_piece_types.map do |u|
383
+ SetMove.new(Piece.new(gamestate.current_player_color, u), d)
384
+ end
385
+ end
386
+ end
231
387
 
232
388
  # Prueft, ob ein Spieler im gegebenen GameState gewonnen hat.
233
389
  # @param gamestate [GameState] Der zu untersuchende GameState.
234
390
  # @return [Condition] nil, if the game is not won or a Condition indicating the winning player
235
391
  def self.winning_condition(gamestate)
392
+ raise "Not implemented yet!"
236
393
  winner_by_single_swarm = [PlayerColor::RED, PlayerColor::BLUE].select do |player_color|
237
394
  GameRuleLogic.swarm_size(gamestate.board, player_color) ==
238
395
  gamestate.board.fields_of_type(PlayerColor.field_type(player_color)).size