software_challenge_client 19.1.0 → 20.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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