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
@@ -0,0 +1,39 @@
1
+ # encoding: UTF-8
2
+
3
+ require 'typesafe_enum'
4
+ # A direction on the hexagonal board of the game. Possible directions are:
5
+ # * RIGHT
6
+ # * UP_RIGHT
7
+ # * UP_LEFT
8
+ # * LEFT
9
+ # * DOWN_LEFT
10
+ # * DOWN_RIGHT
11
+ # Access them as Direction::RIGHT for example.
12
+ class Direction < TypesafeEnum::Base
13
+ new :RIGHT
14
+ new :UP_RIGHT
15
+ new :UP_LEFT
16
+ new :LEFT
17
+ new :DOWN_LEFT
18
+ new :DOWN_RIGHT
19
+
20
+ # @param direction [Direction] starting direction
21
+ # @param turns [Integer] number of turns (positive means turning
22
+ # counterclockwise).
23
+ # @return [Direction] The direction when turning the given number of steps
24
+ # from the given direction.
25
+ def self.get_turn_direction(direction, turns)
26
+ # order of directions is equal to counterclockwise turning
27
+ Direction.find_by_ord((direction.ord + turns) % 6)
28
+ end
29
+
30
+ # @return [Turn] The Turn action to get from from_direction to to_direction.
31
+ def self.from_to(from_direction, to_direction)
32
+ distance = (to_direction.ord - from_direction.ord + 6) % 6
33
+ if distance > 3
34
+ distance = distance - 6
35
+ end
36
+ Turn.new(distance)
37
+ end
38
+
39
+ end
@@ -1,13 +1,8 @@
1
1
  # encoding: UTF-8
2
- require_relative 'player_color'
3
2
  require_relative 'field_type'
4
3
 
5
- # @author Ralf-Tobias Diekert
6
- # A field on the game board
4
+ # A field on the game board.
7
5
  class Field
8
- # @!attribute [rw] ownerColor
9
- # @return [PlayerColor] the field's owner's color
10
- attr_accessor :ownerColor
11
6
  # @!attribute [rw] type
12
7
  # @return [PlayerColor] the field's type
13
8
  attr_accessor :type
@@ -18,26 +13,53 @@ class Field
18
13
  # @return [Integer] the field's y-coordinate
19
14
  attr_reader :y
20
15
 
16
+ # @!attribute [r] direction
17
+ # @return [Integer] the direction of the tile which the field belongs to
18
+ attr_reader :direction
19
+
20
+ # @!attribute [r] index
21
+ # @return [Integer] the index of the tile which the field belongs to
22
+ attr_reader :index
23
+
24
+ # @!attribute [r] points
25
+ # @return [Integer] the points awarded to a player placed on this field additionally to points for current tile and passengers
26
+ attr_reader :points
27
+
21
28
  # Initializer
22
29
  #
23
30
  # @param type [FieldType] field type
24
31
  # @param x [Integer] x-coordinate
25
32
  # @param y [Integer] y-coordinate
26
- def initialize(type, x, y)
27
- self.ownerColor = PlayerColor::NONE
33
+ # @param index [Integer] index of tile
34
+ # @param direction [Integer] direction of tile
35
+ # @param points [Integer] points
36
+ def initialize(type, x, y, index, direction, points)
28
37
  self.type = type
29
38
  @x = x
30
39
  @y = y
40
+ @index = index
41
+ @direction = direction
42
+ @points = points
31
43
  end
32
44
 
33
45
  def ==(another_field)
34
- return self.ownerColor == another_field.ownerColor &&
35
- self.type == another_field.type &&
46
+ return self.type == another_field.type &&
36
47
  self.x == another_field.x &&
37
48
  self.y == another_field.y
38
49
  end
39
50
 
51
+ # @return [Boolean] true if the field may never be moved onto.
52
+ def blocked?
53
+ [FieldType::BLOCKED,
54
+ FieldType::PASSENGER0,
55
+ FieldType::PASSENGER1,
56
+ FieldType::PASSENGER2,
57
+ FieldType::PASSENGER3,
58
+ FieldType::PASSENGER4,
59
+ FieldType::PASSENGER5].include? type
60
+ end
61
+
40
62
  def to_s
41
- return "Field: x = #{self.x}, y = #{self.y}, owner = #{self.ownerColor}, type = #{self.type}"
63
+ return "Field: x = #{self.x}, y = #{self.y}, type = #{self.type}"
42
64
  end
43
- end
65
+ end
@@ -1,9 +1,30 @@
1
1
  # encoding: UTF-8
2
- # @author Ralf-Tobias Diekert
3
- #Fieldtype constants
4
- module FieldType
5
- NORMAL = 1
6
- RED = 2
7
- BLUE = 4
8
- SWAMP = 8
9
- end
2
+
3
+ require 'typesafe_enum'
4
+ # All possible field types:
5
+ # * WATER
6
+ # * BLOCKED
7
+ # * PASSENGER0
8
+ # * PASSENGER1
9
+ # * PASSENGER2
10
+ # * PASSENGER3
11
+ # * PASSENGER4
12
+ # * PASSENGER5
13
+ # * SANDBANK
14
+ # * LOG
15
+ # * GOAL
16
+ # Access them with FieldType::WATER.
17
+ class FieldType < TypesafeEnum::Base
18
+ new :WATER
19
+ new :BLOCKED
20
+ new :PASSENGER0
21
+ new :PASSENGER1
22
+ new :PASSENGER2
23
+ new :PASSENGER3
24
+ new :PASSENGER4
25
+ new :PASSENGER5
26
+ new :SANDBANK
27
+ new :LOG
28
+ new :GOAL
29
+
30
+ end
@@ -0,0 +1,17 @@
1
+ # encoding: UTF-8
2
+ # Exception indicating that a requested field does not exist.
3
+ class FieldUnavailableException < StandardError
4
+ # @!attribute [r] x
5
+ # @return [Integer] the X-coordinate of the requested field.
6
+ attr_reader :x
7
+
8
+ # @!attribute [r] y
9
+ # @return [Integer] the Y-coordinate of the requested field.
10
+ attr_reader :y
11
+
12
+ def initialize(x, y)
13
+ super("Field with coordinates (#{x},#{y}) is not available.")
14
+ @x = x
15
+ @y = y
16
+ end
17
+ end
@@ -1,25 +1,22 @@
1
- # encoding: UTF-8
1
+ # encoding: utf-8
2
2
  require_relative './util/constants'
3
3
  require_relative 'player'
4
4
  require_relative 'board'
5
5
  require_relative 'move'
6
6
  require_relative 'condition'
7
- require_relative 'player_color'
8
7
  require_relative 'field_type'
9
8
 
10
- # @author Ralf-Tobias Diekert
11
- # The state of a game
9
+ # The state of a game, as received from the server.
12
10
  class GameState
13
-
14
11
  # @!attribute [rw] turn
15
12
  # @return [Integer] turn number
16
13
  attr_accessor :turn
17
- # @!attribute [rw] startPlayerColor
14
+ # @!attribute [rw] start_player_color
18
15
  # @return [PlayerColor] the start-player's color
19
- attr_accessor :startPlayerColor
20
- # @!attribute [rw] currentPlayerColor
16
+ attr_accessor :start_player_color
17
+ # @!attribute [rw] current_player_color
21
18
  # @return [PlayerColor] the current player's color
22
- attr_accessor :currentPlayerColor
19
+ attr_accessor :current_player_color
23
20
  # @!attribute [r] red
24
21
  # @return [Player] the red player
25
22
  attr_reader :red
@@ -35,310 +32,146 @@ class GameState
35
32
  # @!attribute [rw] condition
36
33
  # @return [Condition] the winner and winning reason
37
34
  attr_accessor :condition
35
+ # @!attribute [rw] free_acceleration
36
+ # @return [Boolean] true if the free acceleration for this turn is still
37
+ # available.
38
+ attr_accessor :free_acceleration
39
+ alias free_acceleration? free_acceleration
40
+
41
+ # @!attribute [rw] free_turn
42
+ # @return [Boolean] True if the free turning for this turn is still
43
+ # available.
44
+ attr_accessor :free_turn
45
+ alias free_turn? free_turn
46
+
47
+ # @!attribute [rw] additional_free_turn_after_push
48
+ # @return [Boolean] True if the free turning for this turn is still
49
+ # available.
50
+ attr_accessor :additional_free_turn_after_push
51
+ alias additional_free_turn_after_push? additional_free_turn_after_push
52
+
53
+ POINTS_PER_TILE = 5
54
+ POINTS_PER_PASSENGER = 5
38
55
 
39
56
  def initialize
40
- self.currentPlayerColor = PlayerColor::RED
41
- self.startPlayerColor = PlayerColor::RED
42
- self.board = Board.new(true)
57
+ @current_player_color = PlayerColor::RED
58
+ @start_player_color = PlayerColor::RED
59
+ @board = Board.new
60
+ @free_acceleration = true
61
+ @free_turn = true
62
+ @additional_free_turn_after_push = false
43
63
  end
44
64
 
45
65
  # adds a player to the gamestate
46
66
  #
47
67
  # @param player [Player] the player, that will be added
48
- def addPlayer(player)
68
+ def add_player(player)
49
69
  if player.color == PlayerColor::RED
50
70
  @red = player
51
- else if player.color == PlayerColor::BLUE
52
- @blue = player
53
- end
71
+ elsif player.color == PlayerColor::BLUE
72
+ @blue = player
54
73
  end
55
74
  end
56
75
 
57
76
  # gets the current player
58
77
  #
59
78
  # @return [Player] the current player
60
- def currentPlayer
61
- if self.currentPlayerColor == PlayerColor::RED
62
- return self.red
63
- else
64
- return self.blue
79
+ def current_player
80
+ if current_player_color == PlayerColor::RED
81
+ then red
82
+ else blue
65
83
  end
66
84
  end
67
85
 
68
86
  # gets the other (not the current) player
69
87
  #
70
88
  # @return [Player] the other (not the current) player
71
- def otherPlayer
72
- if self.currentPlayerColor == PlayerColor::RED
73
- return self.blue
89
+ def other_player
90
+ if current_player_color == PlayerColor::RED
91
+ return blue
74
92
  else
75
- return self.red
93
+ return red
76
94
  end
77
95
  end
78
96
 
79
97
  # gets the other (not the current) player's color
80
98
  #
81
99
  # @return [PlayerColor] the other (not the current) player's color
82
- def otherPlayerColor
83
- return PlayerColor.opponentColor(self.currentPlayerColor)
84
- end
85
-
86
- # gets the start player
87
- #
88
- # @return [Player] the startPlayer
89
- def startPlayer
90
- if self.startPlayer == PlayerColor::RED
91
- return self.red
92
- else
93
- return self.blue
94
- end
95
- end
96
-
97
- # switches current player
98
- def switchCurrentPlayer
99
- if currentPlayer.color == PlayerColor::RED
100
- @currentPlayer = self.blue
101
- else
102
- @currentPlayer = self.red
103
- end
104
- end
105
-
106
- # prepares next turn and sets the last move
107
- #
108
- # @param [Move] the last move
109
- def prepareNextTurn(lastMove)
110
- @turn++
111
- @lastMove = lastMove;
112
- self.switchCurrentPlayer()
100
+ def other_player_color
101
+ PlayerColor.opponent_color(current_player_color)
113
102
  end
114
103
 
115
104
  # gets the current round
116
105
  #
117
106
  # @return [Integer] the current round
118
107
  def round
119
- return self.turn / 2
120
- end
121
-
122
- # gets all possible moves
123
- #
124
- # @return [Array<Move>] a list of all possible moves
125
- def getPossibleMoves
126
- enemyFieldType = currentPlayer.color == PlayerColor::RED ? FieldType::BLUE : FieldType::RED
127
- moves = Array.new
128
- for x in 0..(Constants::SIZE-1)
129
- for y in 0..(Constants::SIZE-1)
130
- if (self.board.fields[x][y].ownerColor == PlayerColor::NONE &&
131
- self.board.fields[x][y].type != FieldType::SWAMP &&
132
- self.board.fields[x][y].type != enemyFieldType)
133
- moves.push(Move.new(x, y))
134
- end
135
- end
136
- end
137
- return moves
108
+ turn / 2
138
109
  end
139
110
 
140
111
  # performs a move on the gamestate
141
112
  #
142
113
  # @param move [Move] the move, that will be performed
143
114
  # @param player [Player] the player, who makes the move
144
- def perform(move, player)
145
- if !move.nil?
146
- if move.x < Constants::SIZE && move.y < Constants::SIZE &&
147
- move.x >= 0 && move.y >= 0
148
- if self.getPossibleMoves.include?(move)
149
- self.board.put(move.x, move.y, player)
150
- player.points = self.pointsForPlayer(player)
151
- else
152
- raise "Der Zug ist nicht möglich, denn der Platz ist bereits besetzt oder nicht besetzbar."
153
- end
154
- else
155
- raise "Startkoordinaten sind nicht innerhalb des Spielfeldes."
156
- end
157
- end
158
- end
159
-
160
- # gets a player's points
161
- #
162
- # @param player [Player] the player, whos statistics will be returned
163
- # @return [Integer] the points of the player
164
- def playerStats(player)
165
- return self.playerStats(player.color)
166
- end
167
-
168
- # gets a player's points by the player's color
169
- #
170
- # @param playerColor [PlayerColor] the player's color, whos statistics will be returned
171
- # @return [Integer] the points of the player
172
- def playerStats(playerColor)
173
- if playerColor == PlayerColor::RED
174
- return self.gameStats[0];
175
- else
176
- return self.gameStats[1]
177
- end
178
- end
179
-
180
- # gets the players' statistics
181
- #
182
- # @return [Array<Integer>] the points for both players
183
- def gameStats
184
- stats = Array.new(2, Array.new(1))
185
-
186
- stats[0][0] = self.red.points
187
- stats[1][0] = self.blue.points
188
-
189
- return stats
190
- end
191
-
192
- # get the players' names
193
- #
194
- # @return [Array<String>] the names for both players
195
- def playerNames
196
- return [red.displayName, blue.displayName]
197
- end
198
-
199
- # sets the game-ended condition
200
- #
201
- # @param winner [Player] the winner of the game
202
- # @param reason [String] the winning reason
203
- def endGame(winner, reason)
204
- if condition.nil?
205
- @condition = Condition.new(winner, reason)
115
+ def perform!(move, player)
116
+ move.actions.each do |action|
117
+ action.perform!(self, player)
206
118
  end
207
119
  end
208
120
 
209
121
  # has the game ended?
210
122
  #
211
123
  # @return [Boolean] true, if the game has allready ended
212
- def gameEnded?
213
- return !self.condition.nil?
124
+ def game_ended?
125
+ !condition.nil?
214
126
  end
215
127
 
216
128
  # gets the game's winner
217
129
  #
218
130
  # @return [Player] the game's winner
219
131
  def winner
220
- return condition.nil? ? nil : self.condition.winner
132
+ condition.nil? ? nil : condition.winner
221
133
  end
222
134
 
223
135
  # gets the winning reason
224
136
  #
225
137
  # @return [String] the winning reason
226
- def winningReason
227
- return condition.nil? ? nil : self.condition.reason
138
+ def winning_reason
139
+ condition.nil? ? nil : condition.reason
228
140
  end
229
141
 
230
- # calculates a player's points
142
+ # calculates a player's points based on the current gamestate
231
143
  #
232
- # @param player [Player] the player, whos point will be calculated
144
+ # @param player [Player] the player, whos point will be calculated
233
145
  # @return [Integer] the points of the player
234
- def pointsForPlayer(player)
235
- playerColor = player.color
236
- longestPath = 0
237
-
238
- outermostFieldsInCircuit = Array.new(Array.new)
239
- visited = Array.new(Constants::SIZE).map {|j| Array.new(Constants::SIZE).map {|i| false}} #// all by default initialized to false
240
- for x in 0..(Constants::SIZE-1)
241
- for y in 0..(Constants::SIZE-1)
242
- if visited[x][y] == false
243
- if self.board.fields[x][y].ownerColor == playerColor
244
- startOfCircuit = Array.new
245
- startOfCircuit.push(self.board.fields[x][y])
246
- circuit = self.circuit(startOfCircuit, Array.new)
247
- for f in circuit
248
- visited[f.x][f.y] = true
249
- end
250
- outermost = Array.new(2)
251
- if playerColor == PlayerColor::RED
252
-
253
- outermost[0] = self.bottomMostFieldInCircuit(circuit)
254
- outermost[1] = self.topMostFieldInCircuit(circuit)
255
- else
256
- outermost[0] = leftMostFieldInCircuit(circuit)
257
- outermost[1] = rightMostFieldInCircuit(circuit)
258
- end
259
- outermostFieldsInCircuit.push(outermost)
260
-
261
- end
262
- visited[x][y] = true
263
- end
264
- end
265
- end
266
- for fields in outermostFieldsInCircuit
267
- if (playerColor == PlayerColor::RED && fields[1].y - fields[0].y > longestPath)
268
- longestPath = fields[1].y - fields[0].y
269
- end
270
- if (playerColor == PlayerColor::BLUE && fields[1].x - fields[0].x > longestPath)
271
- longestPath = fields[1].x - fields[0].x
272
- end
273
- end
274
-
275
- return longestPath # return longestPath
276
- end
277
-
278
- # the following functions are helpers for the points calculation
279
- def bottomMostFieldInCircuit(circuit)
280
- bottomMostField = circuit[0]
281
- for f in circuit
282
- if f.y < bottomMostField.y
283
- bottomMostField = f
284
- end
285
- end
286
- return bottomMostField
287
- end
288
-
289
- def topMostFieldInCircuit(circuit)
290
- topMostField = circuit[0]
291
- for f in circuit
292
- if f.y > topMostField.y
293
- topMostField = f
294
- end
295
- end
296
- return topMostField
297
- end
298
-
299
- def leftMostFieldInCircuit(circuit)
300
- leftMostField = circuit[0]
301
- for f in circuit
302
- if f.x < leftMostField.x
303
- leftMostField = f
304
- end
305
- end
306
- return leftMostField
307
- end
308
-
309
- def rightMostFieldInCircuit(circuit)
310
- rightMostField = circuit[0]
311
- for f in circuit
312
- if f.x > rightMostField.x
313
- rightMostField = f
314
- end
315
- end
316
- return rightMostField
317
- end
318
-
319
- def circuit(circuit, visited)
320
- changed = false;
321
- toBeAddedFields = Array.new
322
- for f in circuit
323
- if !visited.include?(f)
324
- changed = true
325
- visited.push(f)
326
- for c in self.board.getConnections(f.x,f.y)
327
- if !circuit.include?(self.board.fields[c.x2][c.y2])
328
- toBeAddedFields.push(self.board.fields[c.x2][c.y2])
329
- end
330
- if !circuit.include?(self.board.fields[c.x1][c.y1])
331
- toBeAddedFields.push(self.board.fields[c.x1][c.y1])
332
- end
333
- end
334
- end
335
- end
336
- circuit.push(*toBeAddedFields)
337
- if changed
338
- return self.circuit(circuit, visited)
339
- else
340
- return circuit
341
- end
342
- end
343
-
344
- end
146
+ def points_for_player(player)
147
+ player_field = board.field(player.x, player.y)
148
+ POINTS_PER_TILE * player_field.index +
149
+ POINTS_PER_PASSENGER * player.passengers +
150
+ player_field.points
151
+ end
152
+
153
+ # @return [Boolean] true if the given field is occupied by the other (not
154
+ # current) player.
155
+ def occupied_by_other_player?(field)
156
+ field.x == other_player.x && field.y == other_player.y
157
+ end
158
+
159
+ # Compared with other state.
160
+ def ==(other)
161
+ turn == other.turn &&
162
+ start_player_color == other.start_player_color &&
163
+ current_player_color == other.current_player_color &&
164
+ red == other.red &&
165
+ blue == other.blue &&
166
+ board == other.board &&
167
+ lastMove == other.lastMove &&
168
+ free_acceleration == other.free_acceleration &&
169
+ condition == other.condition
170
+ end
171
+
172
+ # Create a deep copy of the gamestate. Can be used to perform moves on without
173
+ # changing the original gamestate.
174
+ def deep_clone
175
+ Marshal.load(Marshal.dump(self))
176
+ end
177
+ end