software_challenge_client 0.1.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 +7 -0
- data/.gitignore +11 -0
- data/.rspec +2 -0
- data/CODE_OF_CONDUCT.md +13 -0
- data/Gemfile +4 -0
- data/README.md +45 -0
- data/RELEASES.md +3 -0
- data/Rakefile +7 -0
- data/example/client.rb +30 -0
- data/example/main.rb +36 -0
- data/lib/software_challenge_client.rb +17 -0
- data/lib/software_challenge_client/board.rb +315 -0
- data/lib/software_challenge_client/client_interface.rb +7 -0
- data/lib/software_challenge_client/condition.rb +21 -0
- data/lib/software_challenge_client/connection.rb +49 -0
- data/lib/software_challenge_client/debug_hint.rb +33 -0
- data/lib/software_challenge_client/field.rb +42 -0
- data/lib/software_challenge_client/field_type.rb +8 -0
- data/lib/software_challenge_client/game_state.rb +343 -0
- data/lib/software_challenge_client/move.rb +58 -0
- data/lib/software_challenge_client/network.rb +146 -0
- data/lib/software_challenge_client/player.rb +24 -0
- data/lib/software_challenge_client/player_color.rb +23 -0
- data/lib/software_challenge_client/protocol.rb +149 -0
- data/lib/software_challenge_client/runner.rb +33 -0
- data/lib/software_challenge_client/util/constants.rb +5 -0
- data/lib/software_challenge_client/version.rb +3 -0
- data/software_challenge_client.gemspec +24 -0
- metadata +116 -0
@@ -0,0 +1,21 @@
|
|
1
|
+
require_relative 'player'
|
2
|
+
|
3
|
+
# @author Ralf-Tobias Diekert
|
4
|
+
# winning condition
|
5
|
+
class Condition
|
6
|
+
# @!attribute [r] winner
|
7
|
+
# @return [Player] winning player
|
8
|
+
attr_reader :winner
|
9
|
+
# @!attribute [r] reason
|
10
|
+
# @return [String] winning reason
|
11
|
+
attr_reader :reason
|
12
|
+
|
13
|
+
# Initializes the winning Condition with a player and a reason
|
14
|
+
# @param winer [Player] winning player
|
15
|
+
# @param reason [String] winning reason
|
16
|
+
def initialize(winner, reason)
|
17
|
+
@winner = winner
|
18
|
+
@reason = reason
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
require_relative 'player_color'
|
2
|
+
|
3
|
+
# @author Ralf-Tobias Diekert
|
4
|
+
# A connection between two fields owned by a specific player
|
5
|
+
class Connection
|
6
|
+
# @!attribute [r] x1
|
7
|
+
# @return [Integer] x-coordinate starting point
|
8
|
+
attr_reader :x1
|
9
|
+
# @!attribute [r] x2
|
10
|
+
# @return [Integer] y-coordinate starting point
|
11
|
+
attr_reader :x2
|
12
|
+
# @!attribute [r] y1
|
13
|
+
# @return [Integer] x-coordinate ending point
|
14
|
+
attr_reader :y1
|
15
|
+
# @!attribute [r] y2
|
16
|
+
# @return [Integer] y-coordinate ending point
|
17
|
+
attr_reader :y2
|
18
|
+
# @!attribute [r] ownerColor
|
19
|
+
# @return [PlayerColor] connection's owner's color
|
20
|
+
attr_reader :ownerColor
|
21
|
+
|
22
|
+
# Initializer
|
23
|
+
#
|
24
|
+
# @param x1 [Integer] x-coordinate starting point
|
25
|
+
# @param y1 [Integer] y-coordinate starting point
|
26
|
+
# @param x2 [Integer] x-coordinate ending point
|
27
|
+
# @param y2 [Integer] y-coordinate ending point
|
28
|
+
# @param owner [PlayerColor] connection's owner's color
|
29
|
+
def initialize(x1, y1, x2, y2, ownerColor)
|
30
|
+
@x1 = x1
|
31
|
+
@x2 = x2
|
32
|
+
@y1 = y1
|
33
|
+
@y2 = y2
|
34
|
+
@ownerColor = ownerColor
|
35
|
+
end
|
36
|
+
|
37
|
+
def ==(another_connection)
|
38
|
+
if(self.x1 == another_connection.x1 && self.y1 == another_connection.y1 && self.x2 == another_connection.x2 && self.y2 == another_connection.y2 ||
|
39
|
+
self.x1 == another_connection.x2 && self.y1 == another_connection.y2 && self.x2 == another_connection.x1 && self.y2 == another_connection.y1)
|
40
|
+
return ownerColor == c.ownerColor
|
41
|
+
else
|
42
|
+
return false
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def to_s
|
47
|
+
return "#{self.ownerColor} : (#{self.x1}, #{self.y1}) - (#{self.x2}, #{self.y2})"
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# @author Ralf-Tobias Diekert
|
2
|
+
# A debug hint, that can be added to a move
|
3
|
+
class DebugHint
|
4
|
+
|
5
|
+
# @!attribute [r] content
|
6
|
+
# @return [String] a hint
|
7
|
+
attr_reader :content
|
8
|
+
|
9
|
+
# @overload initialize
|
10
|
+
# Creates an empty hint
|
11
|
+
# @overload initialize(key, value)
|
12
|
+
# Creates a hint with a key and a value
|
13
|
+
# @param key Key of the hint
|
14
|
+
# @param value of the hint
|
15
|
+
# @overload initialize(content)
|
16
|
+
# Creates a hint with specified content
|
17
|
+
# @param content of the hint
|
18
|
+
def initialize
|
19
|
+
|
20
|
+
end
|
21
|
+
|
22
|
+
def initialize(key, value)
|
23
|
+
if key.nil?
|
24
|
+
self.content = "#{value}"
|
25
|
+
else
|
26
|
+
self.content = "#{key} = #{value}"
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def initialize(content)
|
31
|
+
self.content = "#{content}"
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require_relative 'player_color'
|
2
|
+
require_relative 'field_type'
|
3
|
+
|
4
|
+
# @author Ralf-Tobias Diekert
|
5
|
+
# A field on the game board
|
6
|
+
class Field
|
7
|
+
# @!attribute [rw] ownerColor
|
8
|
+
# @return [PlayerColor] the field's owner's color
|
9
|
+
attr_accessor :ownerColor
|
10
|
+
# @!attribute [rw] type
|
11
|
+
# @return [PlayerColor] the field's type
|
12
|
+
attr_accessor :type
|
13
|
+
# @!attribute [r] x
|
14
|
+
# @return [Integer] the field's x-coordinate
|
15
|
+
attr_reader :x
|
16
|
+
# @!attribute [r] y
|
17
|
+
# @return [Integer] the field's y-coordinate
|
18
|
+
attr_reader :y
|
19
|
+
|
20
|
+
# Initializer
|
21
|
+
#
|
22
|
+
# @param type [FieldType] field type
|
23
|
+
# @param x [Integer] x-coordinate
|
24
|
+
# @param y [Integer] y-coordinate
|
25
|
+
def initialize(type, x, y)
|
26
|
+
self.ownerColor = PlayerColor::NONE
|
27
|
+
self.type = type
|
28
|
+
@x = x
|
29
|
+
@y = y
|
30
|
+
end
|
31
|
+
|
32
|
+
def ==(another_field)
|
33
|
+
return self.ownerColor == another_field.ownerColor &&
|
34
|
+
self.type == another_field.type &&
|
35
|
+
self.x == another_field.x &&
|
36
|
+
self.y == another_field.y
|
37
|
+
end
|
38
|
+
|
39
|
+
def to_s
|
40
|
+
return "Field: x = #{self.x}, y = #{self.y}, owner = #{self.ownerColor}, type = #{self.type}"
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,343 @@
|
|
1
|
+
require_relative './util/constants'
|
2
|
+
require_relative 'player'
|
3
|
+
require_relative 'board'
|
4
|
+
require_relative 'move'
|
5
|
+
require_relative 'condition'
|
6
|
+
require_relative 'player_color'
|
7
|
+
require_relative 'field_type'
|
8
|
+
|
9
|
+
# @author Ralf-Tobias Diekert
|
10
|
+
# The state of a game
|
11
|
+
class GameState
|
12
|
+
|
13
|
+
# @!attribute [rw] turn
|
14
|
+
# @return [Integer] turn number
|
15
|
+
attr_accessor :turn
|
16
|
+
# @!attribute [rw] startPlayerColor
|
17
|
+
# @return [PlayerColor] the start-player's color
|
18
|
+
attr_accessor :startPlayerColor
|
19
|
+
# @!attribute [rw] currentPlayerColor
|
20
|
+
# @return [PlayerColor] the current player's color
|
21
|
+
attr_accessor :currentPlayerColor
|
22
|
+
# @!attribute [r] red
|
23
|
+
# @return [Player] the red player
|
24
|
+
attr_reader :red
|
25
|
+
# @!attribute [r] blue
|
26
|
+
# @return [Player] the blue player
|
27
|
+
attr_reader :blue
|
28
|
+
# @!attribute [rw] board
|
29
|
+
# @return [Board] the game's board
|
30
|
+
attr_accessor :board
|
31
|
+
# @!attribute [rw] lastMove
|
32
|
+
# @return [Move] the last move, that was made
|
33
|
+
attr_accessor :lastMove
|
34
|
+
# @!attribute [rw] condition
|
35
|
+
# @return [Condition] the winner and winning reason
|
36
|
+
attr_accessor :condition
|
37
|
+
|
38
|
+
def initialize
|
39
|
+
self.currentPlayerColor = PlayerColor::RED
|
40
|
+
self.startPlayerColor = PlayerColor::RED
|
41
|
+
self.board = Board.new(true)
|
42
|
+
end
|
43
|
+
|
44
|
+
# adds a player to the gamestate
|
45
|
+
#
|
46
|
+
# @param player [Player] the player, that will be added
|
47
|
+
def addPlayer(player)
|
48
|
+
if player.color == PlayerColor::RED
|
49
|
+
@red = player
|
50
|
+
else if player.color == PlayerColor::BLUE
|
51
|
+
@blue = player
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
# gets the current player
|
57
|
+
#
|
58
|
+
# @return [Player] the current player
|
59
|
+
def currentPlayer
|
60
|
+
if self.currentPlayerColor == PlayerColor::RED
|
61
|
+
return self.red
|
62
|
+
else
|
63
|
+
return self.blue
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
# gets the other (not the current) player
|
68
|
+
#
|
69
|
+
# @return [Player] the other (not the current) player
|
70
|
+
def otherPlayer
|
71
|
+
if self.currentPlayerColor == PlayerColor::RED
|
72
|
+
return self.blue
|
73
|
+
else
|
74
|
+
return self.red
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
# gets the other (not the current) player's color
|
79
|
+
#
|
80
|
+
# @return [PlayerColor] the other (not the current) player's color
|
81
|
+
def otherPlayerColor
|
82
|
+
return PlayerColor.opponentColor(self.currentPlayerColor)
|
83
|
+
end
|
84
|
+
|
85
|
+
# gets the start player
|
86
|
+
#
|
87
|
+
# @return [Player] the startPlayer
|
88
|
+
def startPlayer
|
89
|
+
if self.startPlayer == PlayerColor::RED
|
90
|
+
return self.red
|
91
|
+
else
|
92
|
+
return self.blue
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
# switches current player
|
97
|
+
def switchCurrentPlayer
|
98
|
+
if currentPlayer.color == PlayerColor::RED
|
99
|
+
@currentPlayer = self.blue
|
100
|
+
else
|
101
|
+
@currentPlayer = self.red
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
# prepares next turn and sets the last move
|
106
|
+
#
|
107
|
+
# @param [Move] the last move
|
108
|
+
def prepareNextTurn(lastMove)
|
109
|
+
@turn++
|
110
|
+
@lastMove = lastMove;
|
111
|
+
self.switchCurrentPlayer()
|
112
|
+
end
|
113
|
+
|
114
|
+
# gets the current round
|
115
|
+
#
|
116
|
+
# @return [Integer] the current round
|
117
|
+
def round
|
118
|
+
return self.turn / 2
|
119
|
+
end
|
120
|
+
|
121
|
+
# gets all possible moves
|
122
|
+
#
|
123
|
+
# @return [Array<Move>] a list of all possible moves
|
124
|
+
def getPossibleMoves
|
125
|
+
enemyFieldType = currentPlayer.color == PlayerColor::RED ? FieldType::BLUE : FieldType::RED
|
126
|
+
moves = Array.new
|
127
|
+
for x in 0..(Constants::SIZE-1)
|
128
|
+
for y in 0..(Constants::SIZE-1)
|
129
|
+
if (self.board.fields[x][y].ownerColor == PlayerColor::NONE &&
|
130
|
+
self.board.fields[x][y].type != FieldType::SWAMP &&
|
131
|
+
self.board.fields[x][y].type != enemyFieldType)
|
132
|
+
moves.push(Move.new(x, y))
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
136
|
+
return moves
|
137
|
+
end
|
138
|
+
|
139
|
+
# performs a move on the gamestate
|
140
|
+
#
|
141
|
+
# @param move [Move] the move, that will be performed
|
142
|
+
# @param player [Player] the player, who makes the move
|
143
|
+
def perform(move, player)
|
144
|
+
if !move.nil?
|
145
|
+
if move.x < Constants::SIZE && move.y < Constants::SIZE &&
|
146
|
+
move.x >= 0 && move.y >= 0
|
147
|
+
if self.getPossibleMoves.include?(move)
|
148
|
+
self.board.put(move.x, move.y, player)
|
149
|
+
player.points = self.pointsForPlayer(player)
|
150
|
+
else
|
151
|
+
raise "Der Zug ist nicht möglich, denn der Platz ist bereits besetzt oder nicht besetzbar."
|
152
|
+
end
|
153
|
+
else
|
154
|
+
raise "Startkoordinaten sind nicht innerhalb des Spielfeldes."
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
# gets a player's points
|
160
|
+
#
|
161
|
+
# @param player [Player] the player, whos statistics will be returned
|
162
|
+
# @return [Integer] the points of the player
|
163
|
+
def playerStats(player)
|
164
|
+
return self.playerStats(player.color)
|
165
|
+
end
|
166
|
+
|
167
|
+
# gets a player's points by the player's color
|
168
|
+
#
|
169
|
+
# @param playerColor [PlayerColor] the player's color, whos statistics will be returned
|
170
|
+
# @return [Integer] the points of the player
|
171
|
+
def playerStats(playerColor)
|
172
|
+
if playerColor == PlayerColor::RED
|
173
|
+
return self.gameStats[0];
|
174
|
+
else
|
175
|
+
return self.gameStats[1]
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
# gets the players' statistics
|
180
|
+
#
|
181
|
+
# @return [Array<Integer>] the points for both players
|
182
|
+
def gameStats
|
183
|
+
stats = Array.new(2, Array.new(1))
|
184
|
+
|
185
|
+
stats[0][0] = self.red.points
|
186
|
+
stats[1][0] = self.blue.points
|
187
|
+
|
188
|
+
return stats
|
189
|
+
end
|
190
|
+
|
191
|
+
# get the players' names
|
192
|
+
#
|
193
|
+
# @return [Array<String>] the names for both players
|
194
|
+
def playerNames
|
195
|
+
return [red.displayName, blue.displayName]
|
196
|
+
end
|
197
|
+
|
198
|
+
# sets the game-ended condition
|
199
|
+
#
|
200
|
+
# @param winner [Player] the winner of the game
|
201
|
+
# @param reason [String] the winning reason
|
202
|
+
def endGame(winner, reason)
|
203
|
+
if condition.nil?
|
204
|
+
@condition = Condition.new(winner, reason)
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
208
|
+
# has the game ended?
|
209
|
+
#
|
210
|
+
# @return [Boolean] true, if the game has allready ended
|
211
|
+
def gameEnded?
|
212
|
+
return !self.condition.nil?
|
213
|
+
end
|
214
|
+
|
215
|
+
# gets the game's winner
|
216
|
+
#
|
217
|
+
# @return [Player] the game's winner
|
218
|
+
def winner
|
219
|
+
return condition.nil? ? nil : self.condition.winner
|
220
|
+
end
|
221
|
+
|
222
|
+
# gets the winning reason
|
223
|
+
#
|
224
|
+
# @return [String] the winning reason
|
225
|
+
def winningReason
|
226
|
+
return condition.nil? ? nil : self.condition.reason
|
227
|
+
end
|
228
|
+
|
229
|
+
# calculates a player's points
|
230
|
+
#
|
231
|
+
# @param player [Player] the player, whos point will be calculated
|
232
|
+
# @return [Integer] the points of the player
|
233
|
+
def pointsForPlayer(player)
|
234
|
+
playerColor = player.color
|
235
|
+
longestPath = 0
|
236
|
+
|
237
|
+
outermostFieldsInCircuit = Array.new(Array.new)
|
238
|
+
visited = Array.new(Constants::SIZE).map {|j| Array.new(Constants::SIZE).map {|i| false}} #// all by default initialized to false
|
239
|
+
for x in 0..(Constants::SIZE-1)
|
240
|
+
for y in 0..(Constants::SIZE-1)
|
241
|
+
if visited[x][y] == false
|
242
|
+
if self.board.fields[x][y].ownerColor == playerColor
|
243
|
+
startOfCircuit = Array.new
|
244
|
+
startOfCircuit.push(self.board.fields[x][y])
|
245
|
+
circuit = self.circuit(startOfCircuit, Array.new)
|
246
|
+
for f in circuit
|
247
|
+
visited[f.x][f.y] = true
|
248
|
+
end
|
249
|
+
outermost = Array.new(2)
|
250
|
+
if playerColor == PlayerColor::RED
|
251
|
+
|
252
|
+
outermost[0] = self.bottomMostFieldInCircuit(circuit)
|
253
|
+
outermost[1] = self.topMostFieldInCircuit(circuit)
|
254
|
+
else
|
255
|
+
outermost[0] = leftMostFieldInCircuit(circuit)
|
256
|
+
outermost[1] = rightMostFieldInCircuit(circuit)
|
257
|
+
end
|
258
|
+
outermostFieldsInCircuit.push(outermost)
|
259
|
+
|
260
|
+
end
|
261
|
+
visited[x][y] = true
|
262
|
+
end
|
263
|
+
end
|
264
|
+
end
|
265
|
+
for fields in outermostFieldsInCircuit
|
266
|
+
if (playerColor == PlayerColor::RED && fields[1].y - fields[0].y > longestPath)
|
267
|
+
longestPath = fields[1].y - fields[0].y
|
268
|
+
end
|
269
|
+
if (playerColor == PlayerColor::BLUE && fields[1].x - fields[0].x > longestPath)
|
270
|
+
longestPath = fields[1].x - fields[0].x
|
271
|
+
end
|
272
|
+
end
|
273
|
+
|
274
|
+
return longestPath # return longestPath
|
275
|
+
end
|
276
|
+
|
277
|
+
# the following functions are helpers for the points calculation
|
278
|
+
def bottomMostFieldInCircuit(circuit)
|
279
|
+
bottomMostField = circuit[0]
|
280
|
+
for f in circuit
|
281
|
+
if f.y < bottomMostField.y
|
282
|
+
bottomMostField = f
|
283
|
+
end
|
284
|
+
end
|
285
|
+
return bottomMostField
|
286
|
+
end
|
287
|
+
|
288
|
+
def topMostFieldInCircuit(circuit)
|
289
|
+
topMostField = circuit[0]
|
290
|
+
for f in circuit
|
291
|
+
if f.y > topMostField.y
|
292
|
+
topMostField = f
|
293
|
+
end
|
294
|
+
end
|
295
|
+
return topMostField
|
296
|
+
end
|
297
|
+
|
298
|
+
def leftMostFieldInCircuit(circuit)
|
299
|
+
leftMostField = circuit[0]
|
300
|
+
for f in circuit
|
301
|
+
if f.x < leftMostField.x
|
302
|
+
leftMostField = f
|
303
|
+
end
|
304
|
+
end
|
305
|
+
return leftMostField
|
306
|
+
end
|
307
|
+
|
308
|
+
def rightMostFieldInCircuit(circuit)
|
309
|
+
rightMostField = circuit[0]
|
310
|
+
for f in circuit
|
311
|
+
if f.x > rightMostField.x
|
312
|
+
rightMostField = f
|
313
|
+
end
|
314
|
+
end
|
315
|
+
return rightMostField
|
316
|
+
end
|
317
|
+
|
318
|
+
def circuit(circuit, visited)
|
319
|
+
changed = false;
|
320
|
+
toBeAddedFields = Array.new
|
321
|
+
for f in circuit
|
322
|
+
if !visited.include?(f)
|
323
|
+
changed = true
|
324
|
+
visited.push(f)
|
325
|
+
for c in self.board.getConnections(f.x,f.y)
|
326
|
+
if !circuit.include?(self.board.fields[c.x2][c.y2])
|
327
|
+
toBeAddedFields.push(self.board.fields[c.x2][c.y2])
|
328
|
+
end
|
329
|
+
if !circuit.include?(self.board.fields[c.x1][c.y1])
|
330
|
+
toBeAddedFields.push(self.board.fields[c.x1][c.y1])
|
331
|
+
end
|
332
|
+
end
|
333
|
+
end
|
334
|
+
end
|
335
|
+
circuit.push(*toBeAddedFields)
|
336
|
+
if changed
|
337
|
+
return self.circuit(circuit, visited)
|
338
|
+
else
|
339
|
+
return circuit
|
340
|
+
end
|
341
|
+
end
|
342
|
+
|
343
|
+
end
|