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.
- 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
|