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