software_challenge_client 0.1.5 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|