software_challenge_client 0.1.5 → 0.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 +4 -4
- data/.gitignore +1 -0
- data/.rspec +1 -0
- data/.rubocop.yml +6 -0
- data/AUTHORS +6 -0
- data/Guardfile +44 -0
- data/README.md +45 -0
- data/RELEASES.md +4 -0
- data/develop.sh +3 -0
- data/example/client.rb +45 -17
- data/example/main.rb +1 -1
- data/generate-authors.sh +19 -0
- data/lib/software_challenge_client.rb +18 -15
- data/lib/software_challenge_client/action.rb +278 -0
- data/lib/software_challenge_client/board.rb +74 -289
- data/lib/software_challenge_client/client_interface.rb +8 -3
- data/lib/software_challenge_client/condition.rb +2 -4
- data/lib/software_challenge_client/debug_hint.rb +3 -25
- data/lib/software_challenge_client/direction.rb +39 -0
- data/lib/software_challenge_client/field.rb +34 -12
- data/lib/software_challenge_client/field_type.rb +29 -8
- data/lib/software_challenge_client/field_unavailable_exception.rb +17 -0
- data/lib/software_challenge_client/game_state.rb +88 -255
- data/lib/software_challenge_client/invalid_move_exception.rb +16 -0
- data/lib/software_challenge_client/logging.rb +24 -0
- data/lib/software_challenge_client/move.rb +36 -35
- data/lib/software_challenge_client/network.rb +16 -19
- data/lib/software_challenge_client/player.rb +47 -10
- data/lib/software_challenge_client/player_color.rb +8 -7
- data/lib/software_challenge_client/protocol.rb +131 -83
- data/lib/software_challenge_client/runner.rb +9 -7
- data/lib/software_challenge_client/version.rb +1 -1
- data/release.sh +9 -0
- data/software_challenge_client.gemspec +20 -8
- metadata +175 -7
- data/lib/software_challenge_client/connection.rb +0 -56
@@ -5,312 +5,97 @@ require_relative 'player'
|
|
5
5
|
require_relative 'field_type'
|
6
6
|
require_relative 'field'
|
7
7
|
|
8
|
-
|
9
|
-
|
10
|
-
# @author Ralf-Tobias Diekert
|
11
|
-
# A representation of a twixt game board
|
8
|
+
# A representation of a mississippi queen game board
|
12
9
|
class Board
|
10
|
+
|
13
11
|
# @!attribute [r] fields
|
14
|
-
# @
|
12
|
+
# @note Better use {#field} to access fields.
|
13
|
+
# @return [Hash<Field>] A field will be stored at the hash of the
|
14
|
+
# coordinate-tuple (2-element-array) of the field.
|
15
15
|
attr_reader :fields
|
16
|
-
# @!attribute [r] connections
|
17
|
-
# @return [Array<Connection>] the board's connections
|
18
|
-
attr_reader :connections
|
19
|
-
|
20
|
-
def initialize
|
21
|
-
self.init
|
22
|
-
end
|
23
16
|
|
24
17
|
# Initializes the board
|
25
|
-
|
26
|
-
|
27
|
-
def initialize(init)
|
28
|
-
if init
|
29
|
-
self.init
|
30
|
-
else
|
31
|
-
self.makeClearBoard
|
32
|
-
end
|
33
|
-
end
|
34
|
-
|
35
|
-
# Initializes the board with swamps
|
36
|
-
def init
|
37
|
-
@fields = Array.new(Constants::SIZE) {Array.new(Constants::SIZE)}
|
38
|
-
@fields[0][0] = Field.new(FieldType::SWAMP, 0, 0)
|
39
|
-
@fields[0][Constants::SIZE - 1] = Field.new(FieldType::SWAMP, 0, Constants::SIZE - 1)
|
40
|
-
@fields[Constants::SIZE - 1][0] = Field.new(FieldType::SWAMP, Constants::SIZE - 1, 0)
|
41
|
-
@fields[Constants::SIZE - 1][Constants::SIZE - 1] = Field.new(FieldType::SWAMP, Constants::SIZE - 1, Constants::SIZE - 1)
|
42
|
-
for x in 1..(Constants::SIZE - 2)
|
43
|
-
@fields[x][0] = Field.new(FieldType::RED, x, 0);
|
44
|
-
@fields[x][Constants::SIZE - 1] = Field.new(FieldType::RED, x, Constants::SIZE - 1);
|
45
|
-
end
|
46
|
-
for y in 1..(Constants::SIZE - 2)
|
47
|
-
@fields[0][y] = Field.new(FieldType::BLUE, 0, y);
|
48
|
-
@fields[Constants::SIZE - 1][y] = Field.new(FieldType::BLUE, Constants::SIZE - 1, y);
|
49
|
-
end
|
50
|
-
for x in 1..(Constants::SIZE - 2)
|
51
|
-
for y in 1..(Constants::SIZE - 2)
|
52
|
-
@fields[x][y] = Field.new(FieldType::NORMAL, x, y);
|
53
|
-
end
|
54
|
-
end
|
55
|
-
self.placeSwamps()
|
56
|
-
@connections = Array.new;
|
57
|
-
end
|
58
|
-
|
59
|
-
# Places swamps at random coordinates
|
60
|
-
def placeSwamps
|
61
|
-
# big swamp
|
62
|
-
x = 1 + SecureRandom.random_number(Constants::SIZE - 4)
|
63
|
-
y = 1 + SecureRandom.random_number(Constants::SIZE - 4)
|
64
|
-
for i in x..(x + 2)
|
65
|
-
for j in y..(y + 2)
|
66
|
-
self.fields[i][j].type = FieldType::SWAMP
|
67
|
-
end
|
68
|
-
end
|
69
|
-
# first medium swamp
|
70
|
-
x = 1 + SecureRandom.random_number(Constants::SIZE - 3)
|
71
|
-
y = 1 + SecureRandom.random_number(Constants::SIZE - 3)
|
72
|
-
for i in x..(x + 1)
|
73
|
-
for j in y..(y + 1)
|
74
|
-
self.fields[i][j].type = FieldType::SWAMP
|
75
|
-
end
|
76
|
-
end
|
77
|
-
# second medium swamp
|
78
|
-
x = 1 + SecureRandom.random_number(Constants::SIZE - 3)
|
79
|
-
y = 1 + SecureRandom.random_number(Constants::SIZE - 3)
|
80
|
-
for i in x..(x + 1)
|
81
|
-
for j in y..(y + 1)
|
82
|
-
self.fields[i][j].type = FieldType::SWAMP
|
83
|
-
end
|
84
|
-
end
|
85
|
-
# little swamp
|
86
|
-
x = 1 + SecureRandom.random_number(Constants::SIZE - 2)
|
87
|
-
y = 1 + SecureRandom.random_number(Constants::SIZE - 2)
|
88
|
-
self.fields[x][y].type = FieldType::SWAMP
|
89
|
-
end
|
90
|
-
|
91
|
-
|
92
|
-
# creates a cleared board
|
93
|
-
def makeClearBoard
|
94
|
-
@fields = Array.new(Constants::SIZE) {Array.new(Constants::SIZE)}
|
95
|
-
@connections = Array.new
|
96
|
-
end
|
97
|
-
|
98
|
-
# gets the owner's color for the field at the coordinate (x, y)
|
99
|
-
#
|
100
|
-
# @param x [Integer] x-coordinate
|
101
|
-
# @param y [Integer] y-coordinate
|
102
|
-
# @return [PlayerColor] owner's color of field (x, y)
|
103
|
-
def getOwnerColor(x, y)
|
104
|
-
return self.fields[x][y].ownerColor
|
105
|
-
end
|
106
|
-
|
107
|
-
|
108
|
-
# sets the owner's color for the field at the coordinate (x, y)
|
109
|
-
#
|
110
|
-
# @param x [Integer] x-coordinate
|
111
|
-
# @param y [Integer] y-coordinate
|
112
|
-
# @param player [Player] new owner of field (x, y)
|
113
|
-
def put(x, y, player)
|
114
|
-
self.fields[x][y].owner = player.color;
|
115
|
-
self.createNewWires(x, y);
|
116
|
-
end
|
117
|
-
|
118
|
-
# creates wires at the coordinate (x, y), if it is possible
|
119
|
-
#
|
120
|
-
# @param x [Integer] x-coordinate
|
121
|
-
# @param y [Integer] y-coordinate
|
122
|
-
def createNewWires(x, y)
|
123
|
-
if self.checkPossibleWire(x, y, x - 2, y - 1)
|
124
|
-
self.createWire(x, y, x - 2, y - 1)
|
125
|
-
end
|
126
|
-
if self.checkPossibleWire(x, y, x - 1, y - 2)
|
127
|
-
self.createWire(x, y, x - 1, y - 2)
|
128
|
-
end
|
129
|
-
if self.checkPossibleWire(x, y, x - 2, y + 1)
|
130
|
-
self.createWire(x, y, x - 2, y + 1)
|
131
|
-
end
|
132
|
-
if self.checkPossibleWire(x, y, x - 1, y + 2)
|
133
|
-
self.createWire(x, y, x - 1, y + 2)
|
134
|
-
end
|
135
|
-
if self.checkPossibleWire(x, y, x + 2, y - 1)
|
136
|
-
self.createWire(x, y, x + 2, y - 1)
|
137
|
-
end
|
138
|
-
if self.checkPossibleWire(x, y, x + 1, y - 2)
|
139
|
-
self.createWire(x, y, x + 1, y - 2)
|
140
|
-
end
|
141
|
-
if self.checkPossibleWire(x, y, x + 2, y + 1)
|
142
|
-
self.createWire(x, y, x + 2, y + 1)
|
143
|
-
end
|
144
|
-
if self.checkPossibleWire(x, y, x + 1, y + 2)
|
145
|
-
self.createWire(x, y, x + 1, y + 2)
|
146
|
-
end
|
147
|
-
|
148
|
-
end
|
149
|
-
|
150
|
-
# creates a new wire
|
151
|
-
#
|
152
|
-
# @param x1 [Integer] x-coordinate starting point
|
153
|
-
# @param y1 [Integer] y-coordinate starting point
|
154
|
-
# @param x2 [Integer] x-coordinate ending point
|
155
|
-
# @param y2 [Integer] y-coordinate ending point
|
156
|
-
def createWire(x1, y1, x2, y2)
|
157
|
-
self.connections.push(Connection.new(x1, y1, x2, y2, self.fields[x1][y1].ownerColor))
|
158
|
-
end
|
159
|
-
|
160
|
-
# checks, if a wire can be placed at specified coordinates
|
161
|
-
#
|
162
|
-
# @param x1 [Integer] x-coordinate starting point
|
163
|
-
# @param y1 [Integer] y-coordinate starting point
|
164
|
-
# @param x2 [Integer] x-coordinate ending point
|
165
|
-
# @param y2 [Integer] y-coordinate ending point
|
166
|
-
# @return [Boolean] 'true', if a wire can be placed at specified coordinates
|
167
|
-
def checkPossibleWire(x1, y1, x2, y2)
|
168
|
-
if x2 < Constants::SIZE && y2 < Constants::SIZE && x2 >= 0 && y2 >= 0
|
169
|
-
if self.fields[x2][y2].ownerColor == self.fields[x1][y1].ownerColor
|
170
|
-
return !self.existsBlockingWire(x1, y1, x2, y2)
|
171
|
-
end
|
172
|
-
end
|
173
|
-
return false
|
18
|
+
def initialize
|
19
|
+
@fields = {}
|
174
20
|
end
|
175
21
|
|
176
|
-
|
177
|
-
|
178
|
-
# @param x1 [Integer] x-coordinate starting point
|
179
|
-
# @param y1 [Integer] y-coordinate starting point
|
180
|
-
# @param x2 [Integer] x-coordinate ending point
|
181
|
-
# @param y2 [Integer] y-coordinate ending point
|
182
|
-
# @return [Boolean] 'true', if another wire would block the creation of a new wire at specified coordinates
|
183
|
-
def existsBlockingWire(x1, y1, x2, y2)
|
184
|
-
smallerX, biggerX = [x1, x2].minmax
|
185
|
-
smallerY, biggerY = [y1, y2].minmax
|
186
|
-
for x in smallerX..biggerX
|
187
|
-
for y in smallerY..biggerY # checks all 6 Fields, from
|
188
|
-
# where there could be
|
189
|
-
# blocking connections
|
190
|
-
if !self.fields[x][y].ownerColor.nil? && (x != x1 || y != y1) &&
|
191
|
-
(x != x2 || y != y2) # excludes the Fields with no owner and
|
192
|
-
# the fields (x1, y2), (x2, y2)
|
193
|
-
# themselves.
|
194
|
-
if self.isWireBlocked(x1, y1, x2, y2, x, y)
|
195
|
-
return true
|
196
|
-
end
|
197
|
-
end
|
198
|
-
end
|
199
|
-
end
|
200
|
-
return false
|
22
|
+
def to_s
|
23
|
+
fields.values.map { |f| f.type.key.to_s[0] }.join(' ')
|
201
24
|
end
|
202
25
|
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
# @return [Array] Array of connections from field (x, y)
|
208
|
-
def getConnections(x, y)
|
209
|
-
xyConnections = Array.new
|
210
|
-
if !self.connections.nil?
|
211
|
-
for c in self.connections
|
212
|
-
if c.x1 == x && c.y1 == y
|
213
|
-
xyConnections.push(Connection.new(x, y, c.x2, c.y2, c.ownerColor))
|
214
|
-
end
|
215
|
-
if c.x2 == x && c.y2 == y
|
216
|
-
xyConnections.push(Connection.new(x, y, c.x1, c.y1, c.ownerColor))
|
217
|
-
end
|
26
|
+
def ==(other)
|
27
|
+
fields.each_with_index do |row, x|
|
28
|
+
row.each_with_index do |field, y|
|
29
|
+
return false if field != other.field(x, y)
|
218
30
|
end
|
219
31
|
end
|
220
|
-
|
221
|
-
end
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
32
|
+
true
|
33
|
+
end
|
34
|
+
|
35
|
+
def add_field(field)
|
36
|
+
fields[[field.x, field.y]] = field
|
37
|
+
end
|
38
|
+
|
39
|
+
# @return [Integer, Integer] The coordinates of the neighbor of the field
|
40
|
+
# specified by given coordinates in specified
|
41
|
+
# direction.
|
42
|
+
def get_neighbor(x, y, direction)
|
43
|
+
directions = {
|
44
|
+
even_row: {
|
45
|
+
Direction::RIGHT.key=> [+1, 0],
|
46
|
+
Direction::UP_RIGHT.key=>[+1, -1],
|
47
|
+
Direction::UP_LEFT.key=>[0, -1],
|
48
|
+
Direction::LEFT.key=>[-1, 0],
|
49
|
+
Direction::DOWN_LEFT.key=>[0, +1],
|
50
|
+
Direction::DOWN_RIGHT.key=>[+1, +1]
|
51
|
+
},
|
52
|
+
odd_row: {
|
53
|
+
Direction::RIGHT.key=> [+1, 0],
|
54
|
+
Direction::UP_RIGHT.key=> [ 0, -1],
|
55
|
+
Direction::UP_LEFT.key=> [-1, -1],
|
56
|
+
Direction::LEFT.key=> [-1, 0],
|
57
|
+
Direction::DOWN_LEFT.key=> [-1, +1],
|
58
|
+
Direction::DOWN_RIGHT.key=> [ 0, +1]
|
59
|
+
}
|
60
|
+
}
|
61
|
+
|
62
|
+
parity = y.odd? ? :odd_row : :even_row
|
63
|
+
dir = directions[parity][direction.key]
|
64
|
+
return x + dir[0], y + dir[1]
|
65
|
+
end
|
66
|
+
|
67
|
+
# @return [Field] The field in given direction with given distance from the
|
68
|
+
# field with given coordinates.
|
69
|
+
def get_in_direction(from_x, from_y, direction, distance = 1)
|
70
|
+
x = from_x
|
71
|
+
y = from_y
|
72
|
+
distance.times do
|
73
|
+
x, y = get_neighbor(x, y, direction)
|
74
|
+
end
|
75
|
+
if !field(x, y).nil?
|
76
|
+
return field(x, y)
|
77
|
+
else
|
78
|
+
raise FieldUnavailableException.new(x, y)
|
242
79
|
end
|
243
|
-
return 2
|
244
80
|
end
|
245
81
|
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
82
|
+
# @return [Array<Field>] A list of fields in given direction up to given
|
83
|
+
# distance from the field with given coordinates.
|
84
|
+
# The start field is excluded.
|
85
|
+
def get_all_in_direction(from_x, from_y, direction, distance = 1)
|
86
|
+
(1..distance).to_a.map do |i|
|
87
|
+
get_in_direction(
|
88
|
+
from_x, from_y, direction, i
|
89
|
+
)
|
254
90
|
end
|
255
|
-
|
256
|
-
if o1 == 0 && onSegment(p1x,p1y, p2x,p2y, q1x,q1y)
|
257
|
-
return true
|
258
|
-
end
|
259
|
-
|
260
|
-
if o2 == 0 && onSegment(p1x,p1y, q2x,q2y, q1x,q1y)
|
261
|
-
return true
|
262
|
-
end
|
263
|
-
|
264
|
-
if o3 == 0 && onSegment(p2x,p2x, p1x,p1y, q2x,q2y)
|
265
|
-
return true
|
266
|
-
end
|
267
|
-
|
268
|
-
if o4 == 0 && onSegment(p2x,p2y, q1x,q1y, q2x,q2y)
|
269
|
-
return true
|
270
|
-
end
|
271
|
-
|
272
|
-
return false
|
273
91
|
end
|
274
92
|
|
275
|
-
#
|
93
|
+
# Access fields of the board.
|
276
94
|
#
|
277
|
-
# @param
|
278
|
-
# @param
|
279
|
-
# @
|
280
|
-
|
281
|
-
|
282
|
-
# @param y [Integer] y-coordinate comparison field
|
283
|
-
# @return [Boolean] 'true', if another wire would block the creation of a new wire at specified coordinates
|
284
|
-
def isWireBlocked(x1, y1, x2, y2, x, y)
|
285
|
-
for c in getConnections(x, y)
|
286
|
-
if self.doIntersect(x1, y1, x2, y2, x, y, c.x2, c.y2)
|
287
|
-
return true
|
288
|
-
end
|
289
|
-
end
|
290
|
-
return false
|
291
|
-
end
|
292
|
-
|
293
|
-
def to_s
|
294
|
-
return self.fields.map { |f| f.map {|i| (i.ownerColor==PlayerColor::RED ? 'R' : (i.ownerColor==PlayerColor::BLUE ? 'B' : (i.type==FieldType::SWAMP ? 'S' : (i.type==FieldType::RED ? 'r' : (i.type==FieldType::BLUE ? 'b' : ' '))))) }.join(",")}.join("\n")
|
295
|
-
end
|
296
|
-
|
297
|
-
def ==(another_board)
|
298
|
-
for x in 0..(Constants.SIZE - 1)
|
299
|
-
for y in 0..(Constants.SIZE - 1)
|
300
|
-
if self.fields[x][y] != another_board.fields[x][y]
|
301
|
-
return false;
|
302
|
-
end
|
303
|
-
end
|
304
|
-
end
|
305
|
-
if self.connections.length != another_board.connections.length
|
306
|
-
return false;
|
307
|
-
end
|
308
|
-
for c in another_board.connections
|
309
|
-
if self.connections.include?(c)
|
310
|
-
return false
|
311
|
-
end
|
312
|
-
end
|
313
|
-
|
314
|
-
return true;
|
95
|
+
# @param x [Integer] The x-coordinate of the field.
|
96
|
+
# @param y [Integer] The y-coordinate of the field.
|
97
|
+
# @return [Field] the field at the given coordinates.
|
98
|
+
def field(x, y)
|
99
|
+
fields[[x,y]]
|
315
100
|
end
|
316
101
|
end
|
@@ -1,8 +1,13 @@
|
|
1
1
|
# encoding: UTF-8
|
2
|
+
|
3
|
+
# The interface a client should implement to work with the gem.
|
2
4
|
class ClientInterface
|
5
|
+
# Is updated by the gem, when a new gamestate is received from the server.
|
3
6
|
attr_accessor :gamestate
|
4
7
|
|
5
|
-
|
6
|
-
|
8
|
+
# Is called when the server requests a move from the client.
|
9
|
+
# @return [Move] Needs to return a valid move.
|
10
|
+
def move_requested
|
11
|
+
raise 'Not yet implemented'
|
7
12
|
end
|
8
|
-
end
|
13
|
+
end
|
@@ -1,8 +1,7 @@
|
|
1
1
|
# encoding: UTF-8
|
2
2
|
require_relative 'player'
|
3
3
|
|
4
|
-
#
|
5
|
-
# winning condition
|
4
|
+
# Represents the winning condition received from the server when the game ended.
|
6
5
|
class Condition
|
7
6
|
# @!attribute [r] winner
|
8
7
|
# @return [Player] winning player
|
@@ -18,5 +17,4 @@ class Condition
|
|
18
17
|
@winner = winner
|
19
18
|
@reason = reason
|
20
19
|
end
|
21
|
-
|
22
|
-
end
|
20
|
+
end
|
@@ -1,34 +1,12 @@
|
|
1
1
|
# encoding: UTF-8
|
2
|
-
# @author Ralf-Tobias Diekert
|
3
2
|
# A debug hint, that can be added to a move
|
4
3
|
class DebugHint
|
5
|
-
|
6
4
|
# @!attribute [r] content
|
7
5
|
# @return [String] a hint
|
8
6
|
attr_reader :content
|
9
7
|
|
10
|
-
# @
|
11
|
-
# Creates an empty hint
|
12
|
-
# @overload initialize(key, value)
|
13
|
-
# Creates a hint with a key and a value
|
14
|
-
# @param key Key of the hint
|
15
|
-
# @param value of the hint
|
16
|
-
# @overload initialize(content)
|
17
|
-
# Creates a hint with specified content
|
18
|
-
# @param content of the hint
|
19
|
-
def initialize
|
20
|
-
|
21
|
-
end
|
22
|
-
|
23
|
-
def initialize(key, value)
|
24
|
-
if key.nil?
|
25
|
-
self.content = "#{value}"
|
26
|
-
else
|
27
|
-
self.content = "#{key} = #{value}"
|
28
|
-
end
|
29
|
-
end
|
30
|
-
|
8
|
+
# @param content [Object] of the hint, will be converted to a string
|
31
9
|
def initialize(content)
|
32
|
-
|
10
|
+
@content = content.to_s
|
33
11
|
end
|
34
|
-
end
|
12
|
+
end
|