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.
Files changed (36) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/.rspec +1 -0
  4. data/.rubocop.yml +6 -0
  5. data/AUTHORS +6 -0
  6. data/Guardfile +44 -0
  7. data/README.md +45 -0
  8. data/RELEASES.md +4 -0
  9. data/develop.sh +3 -0
  10. data/example/client.rb +45 -17
  11. data/example/main.rb +1 -1
  12. data/generate-authors.sh +19 -0
  13. data/lib/software_challenge_client.rb +18 -15
  14. data/lib/software_challenge_client/action.rb +278 -0
  15. data/lib/software_challenge_client/board.rb +74 -289
  16. data/lib/software_challenge_client/client_interface.rb +8 -3
  17. data/lib/software_challenge_client/condition.rb +2 -4
  18. data/lib/software_challenge_client/debug_hint.rb +3 -25
  19. data/lib/software_challenge_client/direction.rb +39 -0
  20. data/lib/software_challenge_client/field.rb +34 -12
  21. data/lib/software_challenge_client/field_type.rb +29 -8
  22. data/lib/software_challenge_client/field_unavailable_exception.rb +17 -0
  23. data/lib/software_challenge_client/game_state.rb +88 -255
  24. data/lib/software_challenge_client/invalid_move_exception.rb +16 -0
  25. data/lib/software_challenge_client/logging.rb +24 -0
  26. data/lib/software_challenge_client/move.rb +36 -35
  27. data/lib/software_challenge_client/network.rb +16 -19
  28. data/lib/software_challenge_client/player.rb +47 -10
  29. data/lib/software_challenge_client/player_color.rb +8 -7
  30. data/lib/software_challenge_client/protocol.rb +131 -83
  31. data/lib/software_challenge_client/runner.rb +9 -7
  32. data/lib/software_challenge_client/version.rb +1 -1
  33. data/release.sh +9 -0
  34. data/software_challenge_client.gemspec +20 -8
  35. metadata +175 -7
  36. 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
- require 'securerandom'
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
- # @return [Array<Array<Field>>] the board's fields
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
- # @param init [Boolean] if 'true', then the board will be initialized with swamps, otherwise the board will be completely empty
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
- # checks, if a blocking wire exists
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
- # gets connections for the coordinate (x, y)
204
- #
205
- # @param x [Integer] x-coordinate
206
- # @param y [Integer] y-coordinate
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
- return xyConnections
221
- end
222
-
223
- # following functions are helper functions for the blocking wire check
224
- #http://www.geeksforgeeks.org/check-if-two-given-line-segments-intersect/
225
- def onSegment(px,py,qx,qy,rx,ry)
226
- if qx <= [px, rx].max && qx >= [px, rx].min &&
227
- qy <= [py, ry].max && qy >= [py, ry].min
228
- return true
229
- end
230
- return false
231
- end
232
-
233
- def orientation(px,py,qx,qy,rx,ry)
234
- val = (qy - py) * (rx - qx) -
235
- (qx - px) * (ry - qy)
236
-
237
- if val == 0
238
- return 0
239
- end
240
- if val > 0
241
- return 1
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
- def doIntersect(p1x,p1y, q1x,q1y, p2x,p2y, q2x,q2y)
247
- o1 = orientation(p1x,p1y, q1x,q1y, p2x,p2y)
248
- o2 = orientation(p1x,p1y, q1x,q1y, q2x,q2y)
249
- o3 = orientation(p2x,p2y, q2x,q2y, p1x,p1y)
250
- o4 = orientation(p2x,p2y, q2x,q2y, q1x,q1y)
251
-
252
- if o1 != o2 && o3 != o4
253
- return true
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
- # checks for the wire (x1, y1) -> (x2, y2), if it is blocked by any connection going out from (x,y).
93
+ # Access fields of the board.
276
94
  #
277
- # @param x1 [Integer] x-coordinate starting point
278
- # @param y1 [Integer] y-coordinate starting point
279
- # @param x2 [Integer] x-coordinate ending point
280
- # @param y2 [Integer] y-coordinate ending point
281
- # @param x [Integer] x-coordinate comparison field
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
- def getMove
6
- raise "Not yet implemented"
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
- # @author Ralf-Tobias Diekert
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
- # @overload initialize
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
- self.content = "#{content}"
10
+ @content = content.to_s
33
11
  end
34
- end
12
+ end