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.
- checksums.yaml +5 -5
- data/.ruby-version +1 -1
- data/.stickler.yml +7 -0
- data/RELEASES.md +4 -0
- data/example/client.rb +12 -1
- data/lib/software_challenge_client.rb +6 -4
- data/lib/software_challenge_client/board.rb +59 -40
- data/lib/software_challenge_client/cube_coordinates.rb +23 -0
- data/lib/software_challenge_client/direction.rb +23 -21
- data/lib/software_challenge_client/drag_move.rb +19 -0
- data/lib/software_challenge_client/field.rb +58 -21
- data/lib/software_challenge_client/game_rule_logic.rb +355 -198
- data/lib/software_challenge_client/game_state.rb +44 -15
- data/lib/software_challenge_client/has_hints.rb +11 -0
- data/lib/software_challenge_client/invalid_move_exception.rb +2 -2
- data/lib/software_challenge_client/network.rb +0 -2
- data/lib/software_challenge_client/piece.rb +31 -0
- data/lib/software_challenge_client/piece_type.rb +18 -0
- data/lib/software_challenge_client/player_color.rb +6 -16
- data/lib/software_challenge_client/protocol.rb +86 -18
- data/lib/software_challenge_client/set_move.rb +15 -0
- data/lib/software_challenge_client/skip_move.rb +8 -0
- data/lib/software_challenge_client/util/constants.rb +4 -2
- data/lib/software_challenge_client/version.rb +1 -1
- metadata +11 -9
- data/lib/software_challenge_client/coordinates.rb +0 -17
- data/lib/software_challenge_client/field_type.rb +0 -30
- data/lib/software_challenge_client/line.rb +0 -126
- data/lib/software_challenge_client/move.rb +0 -84
@@ -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
|
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
|
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
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
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
|
-
|
98
|
+
true
|
54
99
|
end
|
55
100
|
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
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
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
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
|
-
|
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
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
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
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
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
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
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
|