software_challenge_client 0.3.4 → 1.0.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 +4 -4
- data/.rubocop.yml +3 -1
- data/.ruby-version +1 -0
- data/Guardfile +5 -5
- data/RELEASES.md +6 -0
- data/example/client.rb +71 -33
- data/lib/software_challenge_client.rb +1 -1
- data/lib/software_challenge_client/action.rb +116 -196
- data/lib/software_challenge_client/board.rb +17 -73
- data/lib/software_challenge_client/card_type.rb +13 -0
- data/lib/software_challenge_client/field.rb +10 -44
- data/lib/software_challenge_client/field_type.rb +20 -26
- data/lib/software_challenge_client/game_rules.rb +378 -0
- data/lib/software_challenge_client/game_state.rb +167 -49
- data/lib/software_challenge_client/move.rb +23 -13
- data/lib/software_challenge_client/player.rb +35 -34
- data/lib/software_challenge_client/player_color.rb +1 -1
- data/lib/software_challenge_client/protocol.rb +82 -39
- data/lib/software_challenge_client/version.rb +1 -1
- metadata +6 -5
- data/lib/software_challenge_client/direction.rb +0 -39
@@ -26,40 +26,27 @@ class GameState
|
|
26
26
|
# @!attribute [rw] board
|
27
27
|
# @return [Board] the game's board
|
28
28
|
attr_accessor :board
|
29
|
-
# @!attribute [rw]
|
30
|
-
# @return [Move] the last move
|
31
|
-
attr_accessor :
|
29
|
+
# @!attribute [rw] last_move
|
30
|
+
# @return [Move] the last move performed
|
31
|
+
attr_accessor :last_move
|
32
32
|
# @!attribute [rw] condition
|
33
33
|
# @return [Condition] the winner and winning reason
|
34
34
|
attr_accessor :condition
|
35
|
-
# @!attribute [rw]
|
36
|
-
# @return [Boolean] true if the
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
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
|
35
|
+
# @!attribute [rw] has_to_play_card
|
36
|
+
# @return [Boolean] true if the current player has to play a card
|
37
|
+
attr_accessor :has_to_play_card
|
38
|
+
alias has_to_play_card? has_to_play_card
|
39
|
+
|
40
|
+
def field(index)
|
41
|
+
board.field(index)
|
42
|
+
end
|
55
43
|
|
56
44
|
def initialize
|
57
45
|
@current_player_color = PlayerColor::RED
|
58
46
|
@start_player_color = PlayerColor::RED
|
59
47
|
@board = Board.new
|
60
|
-
@
|
61
|
-
@
|
62
|
-
@additional_free_turn_after_push = false
|
48
|
+
@has_to_play_card = false
|
49
|
+
@turn = 0
|
63
50
|
end
|
64
51
|
|
65
52
|
# adds a player to the gamestate
|
@@ -77,21 +64,16 @@ class GameState
|
|
77
64
|
#
|
78
65
|
# @return [Player] the current player
|
79
66
|
def current_player
|
80
|
-
if current_player_color == PlayerColor::RED
|
81
|
-
|
82
|
-
else blue
|
83
|
-
end
|
67
|
+
return red if current_player_color == PlayerColor::RED
|
68
|
+
return blue if current_player_color == PlayerColor::BLUE
|
84
69
|
end
|
85
70
|
|
86
71
|
# gets the other (not the current) player
|
87
72
|
#
|
88
73
|
# @return [Player] the other (not the current) player
|
89
74
|
def other_player
|
90
|
-
if current_player_color == PlayerColor::RED
|
91
|
-
|
92
|
-
else
|
93
|
-
return red
|
94
|
-
end
|
75
|
+
return blue if current_player_color == PlayerColor::RED
|
76
|
+
return red if current_player_color == PlayerColor::BLUE
|
95
77
|
end
|
96
78
|
|
97
79
|
# gets the other (not the current) player's color
|
@@ -141,32 +123,55 @@ class GameState
|
|
141
123
|
|
142
124
|
# calculates a player's points based on the current gamestate
|
143
125
|
#
|
144
|
-
# @param player [Player] the player, whos
|
126
|
+
# @param player [Player] the player, whos points to calculate
|
145
127
|
# @return [Integer] the points of the player
|
146
128
|
def points_for_player(player)
|
147
|
-
|
148
|
-
POINTS_PER_TILE * player_field.index +
|
149
|
-
POINTS_PER_PASSENGER * player.passengers +
|
150
|
-
player_field.points
|
129
|
+
player.index
|
151
130
|
end
|
152
131
|
|
153
132
|
# @return [Boolean] true if the given field is occupied by the other (not
|
154
133
|
# current) player.
|
155
134
|
def occupied_by_other_player?(field)
|
156
|
-
field.
|
135
|
+
field.index == other_player.index
|
136
|
+
end
|
137
|
+
|
138
|
+
# Searches backwards starting at the field with the given index. The field
|
139
|
+
# at the given index is not considered. If no field of the given type exists,
|
140
|
+
# nil is returned.
|
141
|
+
#
|
142
|
+
# @param [FieldType] type of the field to search for
|
143
|
+
# @param [Integer] index before which field-index to start searching
|
144
|
+
# @return [Field] the next field before the given index of given type
|
145
|
+
def previous_field_of_type(type, index)
|
146
|
+
return nil if index < 1
|
147
|
+
return nil if index >= board.fields.size
|
148
|
+
board.fields.slice(0..(index - 1)).reverse.find {|f| f.type == type}
|
149
|
+
end
|
150
|
+
|
151
|
+
# Searches forward starting at the field with the given index. The field
|
152
|
+
# at the given index is not considered. If no field of the given type exists,
|
153
|
+
# nil is returned.
|
154
|
+
#
|
155
|
+
# @param [FieldType] type of the field to search for
|
156
|
+
# @param [Integer] index after which field-index to start searching
|
157
|
+
# @return [Field] the next field after the given index of given type
|
158
|
+
def next_field_of_type(type, index)
|
159
|
+
return nil if index >= board.fields.size
|
160
|
+
return nil if index < 0
|
161
|
+
board.fields.slice((index + 1)..(board.fields.size - 1)).find {|f| f.type == type}
|
157
162
|
end
|
158
163
|
|
159
164
|
# Compared with other state.
|
160
165
|
def ==(other)
|
161
166
|
turn == other.turn &&
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
167
|
+
start_player_color == other.start_player_color &&
|
168
|
+
current_player_color == other.current_player_color &&
|
169
|
+
red == other.red &&
|
170
|
+
blue == other.blue &&
|
171
|
+
board == other.board &&
|
172
|
+
lastMove == other.lastMove &&
|
173
|
+
has_to_play_card == other.has_to_play_card &&
|
174
|
+
condition == other.condition
|
170
175
|
end
|
171
176
|
|
172
177
|
# Create a deep copy of the gamestate. Can be used to perform moves on without
|
@@ -174,4 +179,117 @@ class GameState
|
|
174
179
|
def deep_clone
|
175
180
|
Marshal.load(Marshal.dump(self))
|
176
181
|
end
|
182
|
+
|
183
|
+
def set_last_action(action)
|
184
|
+
return if action.instance_of? Skip
|
185
|
+
current_player.last_non_skip_action = action
|
186
|
+
end
|
187
|
+
|
188
|
+
def current_field
|
189
|
+
field(current_player.index)
|
190
|
+
end
|
191
|
+
|
192
|
+
def is_first(player)
|
193
|
+
if PlayerColor.opponent_color(player.color) == PlayerColor::RED
|
194
|
+
player.index > red.index
|
195
|
+
else
|
196
|
+
player.index > blue.index
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
def is_second(player)
|
201
|
+
!is_first(player)
|
202
|
+
end
|
203
|
+
|
204
|
+
def switch_current_player
|
205
|
+
current_player_color = other_player_color
|
206
|
+
end
|
207
|
+
|
208
|
+
def possible_moves
|
209
|
+
found_moves = []
|
210
|
+
if GameRules.is_valid_to_eat(self)[0]
|
211
|
+
# Wenn ein Salat gegessen werden kann, muss auch ein Salat gegessen werden
|
212
|
+
found_moves << Move.new([EatSalad.new])
|
213
|
+
return found_moves
|
214
|
+
end
|
215
|
+
if GameRules.is_valid_to_exchange_carrots(self, 10)[0]
|
216
|
+
found_moves << Move.new([ExchangeCarrots.new(10)])
|
217
|
+
end
|
218
|
+
if GameRules.is_valid_to_exchange_carrots(self, -10)[0]
|
219
|
+
found_moves << Move.new([ExchangeCarrots.new(-10)])
|
220
|
+
end
|
221
|
+
if GameRules.is_valid_to_fall_back(self)[0]
|
222
|
+
found_moves << Move.new([FallBack.new])
|
223
|
+
end
|
224
|
+
# Generiere mögliche Vorwärtszüge
|
225
|
+
(1..(GameRules.calculate_movable_fields(self.current_player.carrots))).each do |distance|
|
226
|
+
actions = []
|
227
|
+
clone = self.deep_clone
|
228
|
+
# Überprüfe ob Vorwärtszug möglich ist
|
229
|
+
if GameRules.is_valid_to_advance(clone, distance)[0]
|
230
|
+
try_advance = Advance.new(distance)
|
231
|
+
try_advance.perform!(clone)
|
232
|
+
actions << try_advance
|
233
|
+
# überprüfe, ob eine Karte gespielt werden muss/kann
|
234
|
+
if GameRules.must_play_card(clone)
|
235
|
+
clone.check_for_playable_cards(actions).each do |card|
|
236
|
+
found_moves << card # TODO: this is unexpected, rename or refactor
|
237
|
+
end
|
238
|
+
else
|
239
|
+
# Füge möglichen Vorwärtszug hinzu
|
240
|
+
found_moves << Move.new(actions)
|
241
|
+
end
|
242
|
+
end
|
243
|
+
end
|
244
|
+
if found_moves.empty?
|
245
|
+
found_moves << Move.new([Skip.new])
|
246
|
+
end
|
247
|
+
found_moves
|
248
|
+
end
|
249
|
+
|
250
|
+
# @return [Array[Move]] gibt liste von Zuegen zurueck, die gemacht werden koennen, vorausgesetzt die gegebene Liste von Aktionen wurde schon gemacht.
|
251
|
+
def check_for_playable_cards(actions)
|
252
|
+
found_card_playing_moves = []
|
253
|
+
if current_player.must_play_card
|
254
|
+
if GameRules.is_valid_to_play_eat_salad(self)[0]
|
255
|
+
found_card_playing_moves << Move.new(actions + [Card.new(CardType::EAT_SALAD)])
|
256
|
+
end
|
257
|
+
[20, -20, 0].each do |carrots|
|
258
|
+
if GameRules.is_valid_to_play_take_or_drop_carrots(self,carrots)[0]
|
259
|
+
found_card_playing_moves << Move.new(actions + [Card.new(CardType::TAKE_OR_DROP_CARROTS, carrots)])
|
260
|
+
end
|
261
|
+
end
|
262
|
+
if GameRules.is_valid_to_play_hurry_ahead(self)[0]
|
263
|
+
actions_with_card_played = actions + [Card.new(CardType::HURRY_AHEAD)]
|
264
|
+
# pruefe, ob auf Hasenfeld gelandet
|
265
|
+
clone = self.deep_clone
|
266
|
+
Card.new(CardType::HURRY_AHEAD).perform!(clone)
|
267
|
+
moves = []
|
268
|
+
if GameRules.must_play_card(clone)
|
269
|
+
moves = clone.check_for_playable_cards(actions_with_card_played)
|
270
|
+
end
|
271
|
+
if moves.empty?
|
272
|
+
found_card_playing_moves << Move.new(actions_with_card_played)
|
273
|
+
else
|
274
|
+
found_card_playing_moves += moves
|
275
|
+
end
|
276
|
+
end
|
277
|
+
if GameRules.is_valid_to_play_fall_back(self)[0]
|
278
|
+
actions_with_card_played = actions + [Card.new(CardType::FALL_BACK)]
|
279
|
+
# pruefe, ob auf Hasenfeld gelandet
|
280
|
+
clone = self.deep_clone
|
281
|
+
Card.new(CardType::FALL_BACK).perform!(clone)
|
282
|
+
moves = []
|
283
|
+
if GameRules.must_play_card(clone)
|
284
|
+
moves = clone.check_for_playable_cards(actions_with_card_played)
|
285
|
+
end
|
286
|
+
if moves.empty?
|
287
|
+
found_card_playing_moves << Move.new(actions_with_card_played)
|
288
|
+
else
|
289
|
+
found_card_playing_moves += moves
|
290
|
+
end
|
291
|
+
end
|
292
|
+
end
|
293
|
+
found_card_playing_moves
|
294
|
+
end
|
177
295
|
end
|
@@ -17,9 +17,9 @@ class Move
|
|
17
17
|
|
18
18
|
# Initializer
|
19
19
|
#
|
20
|
-
def initialize
|
21
|
-
@actions =
|
22
|
-
@hints =
|
20
|
+
def initialize(actions = [], hints = [])
|
21
|
+
@actions = actions
|
22
|
+
@hints = hints
|
23
23
|
end
|
24
24
|
|
25
25
|
# adds a hint to the move
|
@@ -45,16 +45,26 @@ class Move
|
|
45
45
|
@actions[index] = action
|
46
46
|
end
|
47
47
|
|
48
|
-
def perform!(gamestate
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
48
|
+
def perform!(gamestate)
|
49
|
+
raise InvalidMoveException.new(
|
50
|
+
"Zug enthält keine Aktionen (zum Aussetzen die Aktion Skip benutzen).",
|
51
|
+
self) if @actions.empty?
|
52
|
+
@actions.each do |action|
|
53
|
+
action.perform!(gamestate)
|
54
|
+
end
|
55
|
+
raise InvalidMoveException.new(
|
56
|
+
'Es muss eine Karte gespielt werden.',
|
57
|
+
self) if gamestate.current_player.must_play_card
|
58
|
+
# change the state to the next turn
|
59
|
+
gamestate.last_move = self
|
60
|
+
gamestate.turn += 1
|
61
|
+
gamestate.switch_current_player
|
62
|
+
# change carrots for next player if on first/second-position-field
|
63
|
+
if gamestate.current_field.type == FieldType::POSITION_1 && gamestate.is_first(gamestate.current_player)
|
64
|
+
gamestate.current_player.carrots += 10
|
65
|
+
end
|
66
|
+
if gamestate.current_field.type == FieldType::POSITION_2 && gamestate.is_second(gamestate.current_player)
|
67
|
+
gamestate.current_player.carrots += 30
|
57
68
|
end
|
58
|
-
@actions.each { |a| a.perform!(gamestate, current_player) }
|
59
69
|
end
|
60
70
|
end
|
@@ -1,62 +1,63 @@
|
|
1
1
|
# encoding: UTF-8
|
2
|
+
require_relative 'card_type'
|
2
3
|
|
3
|
-
#
|
4
|
+
# Ein Spieler
|
4
5
|
class Player
|
5
6
|
# @!attribute [r] name
|
6
|
-
# @return [String]
|
7
|
+
# @return [String] der Name des Spielers, hat keine Auswirkungen auf das Spiel
|
7
8
|
attr_reader :name
|
8
9
|
|
9
10
|
# @!attribute [r] color
|
10
|
-
# @return [PlayerColor]
|
11
|
+
# @return [PlayerColor] die Farbe des Spielers, Rot oder Blau
|
11
12
|
attr_reader :color
|
12
13
|
|
13
14
|
# @!attribute [rw] points
|
14
|
-
# @return [Integer]
|
15
|
+
# @return [Integer] der aktuelle Punktestand des Spielers
|
15
16
|
attr_accessor :points
|
16
17
|
|
17
|
-
# @!attribute [rw]
|
18
|
-
# @return [Integer]
|
19
|
-
|
18
|
+
# @!attribute [rw] index
|
19
|
+
# @return [Integer] die aktuelle Position des Spielers auf dem Spielbrett,
|
20
|
+
# entspricht index des Feldes, von 0 bis 64
|
21
|
+
attr_accessor :index
|
20
22
|
|
21
|
-
# @!attribute [rw]
|
22
|
-
# @return [Integer]
|
23
|
-
attr_accessor :
|
23
|
+
# @!attribute [rw] carrots
|
24
|
+
# @return [Integer] die aktuelle Anzahl Karotten des Spielers
|
25
|
+
attr_accessor :carrots
|
24
26
|
|
25
|
-
# @!attribute [rw]
|
26
|
-
# @return [
|
27
|
-
attr_accessor :
|
27
|
+
# @!attribute [rw] salads
|
28
|
+
# @return [Integer] die aktuelle Anzahl Salate des Spielers
|
29
|
+
attr_accessor :salads
|
28
30
|
|
29
|
-
# @!attribute [rw]
|
30
|
-
# @return [
|
31
|
-
attr_accessor :
|
31
|
+
# @!attribute [rw] cards
|
32
|
+
# @return [Array[CardType]] die noch nicht gespielten Karten
|
33
|
+
attr_accessor :cards
|
32
34
|
|
33
|
-
# @!attribute [rw]
|
34
|
-
# @return [
|
35
|
-
attr_accessor :
|
35
|
+
# @!attribute [rw] last_non_skip_action
|
36
|
+
# @return [Action] letzte Aktion, die kein Skip war
|
37
|
+
attr_accessor :last_non_skip_action
|
36
38
|
|
37
|
-
# @!attribute [rw]
|
38
|
-
# @return [
|
39
|
-
attr_accessor :
|
39
|
+
# @!attribute [rw] must_play_card
|
40
|
+
# @return [Boolean] zeigt an, ob eine Karte gespielt werden muss, wird in Zugvalidierung verwendet.
|
41
|
+
attr_accessor :must_play_card
|
40
42
|
|
41
|
-
#
|
42
|
-
# @
|
43
|
-
|
44
|
-
|
45
|
-
# Initializer
|
46
|
-
# @param color [PlayerColor] the new player's color
|
47
|
-
# @param name [String] the new player's name (for displaying)
|
43
|
+
# Konstruktor
|
44
|
+
# @param color [PlayerColor] Farbe
|
45
|
+
# @param name [String] Name
|
48
46
|
def initialize(color, name)
|
49
47
|
@color = color
|
50
48
|
@name = name
|
51
49
|
@points = 0
|
52
|
-
@
|
53
|
-
@
|
54
|
-
@
|
55
|
-
@
|
56
|
-
@direction = Direction::RIGHT
|
50
|
+
@index = 0
|
51
|
+
@carrots = 68
|
52
|
+
@salads = 2
|
53
|
+
@cards = CardType.to_a
|
57
54
|
end
|
58
55
|
|
59
56
|
def ==(other)
|
60
57
|
color == other.color
|
61
58
|
end
|
59
|
+
|
60
|
+
def owns_card_of_type(card_type)
|
61
|
+
cards.include? card_type
|
62
|
+
end
|
62
63
|
end
|
@@ -11,7 +11,7 @@ class PlayerColor < TypesafeEnum::Base
|
|
11
11
|
#
|
12
12
|
# @param color [PlayerColor] The player's color, whose opponent needs to be found
|
13
13
|
# @return [PlayerColor] the opponent's color
|
14
|
-
def self.
|
14
|
+
def self.opponent_color(color)
|
15
15
|
if color == PlayerColor::RED
|
16
16
|
return PlayerColor::BLUE
|
17
17
|
end
|
@@ -43,6 +43,12 @@ class Protocol
|
|
43
43
|
REXML::Document.parse_stream(text, self)
|
44
44
|
end
|
45
45
|
|
46
|
+
|
47
|
+
# called when text is encountered
|
48
|
+
def text(text)
|
49
|
+
@context[:last_text] = text
|
50
|
+
end
|
51
|
+
|
46
52
|
# called if an end-tag is read
|
47
53
|
#
|
48
54
|
# @param name [String] the end-tag name, that was read
|
@@ -50,9 +56,8 @@ class Protocol
|
|
50
56
|
case name
|
51
57
|
when 'board'
|
52
58
|
logger.debug @gamestate.board.to_s
|
53
|
-
when '
|
54
|
-
|
55
|
-
@network.disconnect
|
59
|
+
when 'type'
|
60
|
+
@context[:player].cards << CardType.find_by_key(@context[:last_text].to_sym)
|
56
61
|
end
|
57
62
|
end
|
58
63
|
|
@@ -79,13 +84,16 @@ class Protocol
|
|
79
84
|
logger.info "Game ended - ERROR: #{attrs['message']}"
|
80
85
|
@network.disconnect
|
81
86
|
end
|
87
|
+
if attrs['class'] == 'result'
|
88
|
+
logger.info 'Got game result'
|
89
|
+
@network.disconnect
|
90
|
+
end
|
82
91
|
when 'state'
|
83
92
|
logger.debug 'new gamestate'
|
84
93
|
@gamestate = GameState.new
|
85
94
|
@gamestate.turn = attrs['turn'].to_i
|
86
95
|
@gamestate.start_player_color = attrs['startPlayer'] == 'RED' ? PlayerColor::RED : PlayerColor::BLUE
|
87
96
|
@gamestate.current_player_color = attrs['currentPlayer'] == 'RED' ? PlayerColor::RED : PlayerColor::BLUE
|
88
|
-
@gamestate.additional_free_turn_after_push = attrs['freeTurn'] == 'true'
|
89
97
|
logger.debug "Turn: #{@gamestate.turn}"
|
90
98
|
when 'red'
|
91
99
|
logger.debug 'new red player'
|
@@ -94,6 +102,7 @@ class Protocol
|
|
94
102
|
throw new IllegalArgumentException("expected #{PlayerColor::RED} Player but got #{player.color}")
|
95
103
|
end
|
96
104
|
@gamestate.add_player(player)
|
105
|
+
@context[:player] = player
|
97
106
|
when 'blue'
|
98
107
|
logger.debug 'new blue player'
|
99
108
|
player = parsePlayer(attrs)
|
@@ -101,39 +110,69 @@ class Protocol
|
|
101
110
|
throw new IllegalArgumentException("expected #{PlayerColor::BLUE} Player but got #{player.color}")
|
102
111
|
end
|
103
112
|
@gamestate.add_player(player)
|
113
|
+
@context[:player] = player
|
104
114
|
when 'board'
|
105
115
|
logger.debug 'new board'
|
106
116
|
@gamestate.board = Board.new
|
107
117
|
@context[:current_tile_index] = nil
|
108
118
|
@context[:current_tile_direction] = nil
|
109
|
-
when '
|
110
|
-
@context[:current_tile_index] = attrs['index'].to_i
|
111
|
-
@context[:current_tile_direction] = attrs['direction'].to_i
|
112
|
-
when 'field'
|
119
|
+
when 'fields'
|
113
120
|
type = FieldType.find_by_key(attrs['type'].to_sym)
|
121
|
+
index = attrs['index'].to_i
|
114
122
|
raise "unexpected field type: #{attrs['type']}. Known types are #{FieldType.map { |t| t.key.to_s }}" if type.nil?
|
115
|
-
|
116
|
-
y = attrs['y'].to_i
|
117
|
-
points = attrs['points'].to_i
|
118
|
-
index = @context[:current_tile_index]
|
119
|
-
direction = @context[:current_tile_direction]
|
120
|
-
|
121
|
-
@gamestate.board.fields[[x, y]] = Field.new(type, x, y, index, direction, points)
|
123
|
+
@gamestate.board.fields[index] = Field.new(type, index)
|
122
124
|
when 'lastMove'
|
123
|
-
@gamestate.
|
124
|
-
when 'acceleration'
|
125
|
-
@gamestate.lastMove.add_action_with_order(Acceleration.new(attrs['acc'].to_i), attrs['order'].to_i)
|
125
|
+
@gamestate.last_move = Move.new
|
126
126
|
when 'advance'
|
127
|
-
@gamestate.
|
128
|
-
|
129
|
-
|
130
|
-
when '
|
131
|
-
@gamestate.
|
127
|
+
@gamestate.last_move.add_action_with_order(
|
128
|
+
Advance.new(attrs['distance'].to_i), attrs['order'].to_i
|
129
|
+
)
|
130
|
+
when 'card'
|
131
|
+
@gamestate.last_move.add_action_with_order(
|
132
|
+
Card.new(CardType.find_by_key(attrs['type'].to_sym), attrs['value'].to_i),
|
133
|
+
attrs['order'].to_i
|
134
|
+
)
|
135
|
+
when 'skip'
|
136
|
+
@gamestate.last_move.add_action_with_order(
|
137
|
+
Skip.new, attrs['order'].to_i
|
138
|
+
)
|
139
|
+
when 'eatSalad'
|
140
|
+
@gamestate.last_move.add_action_with_order(
|
141
|
+
EatSalad.new, attrs['order'].to_i
|
142
|
+
)
|
143
|
+
when 'fallBack'
|
144
|
+
@gamestate.last_move.add_action_with_order(
|
145
|
+
FallBack.new, attrs['order'].to_i
|
146
|
+
)
|
147
|
+
when 'exchangeCarrots'
|
148
|
+
@gamestate.last_move.add_action_with_order(
|
149
|
+
ExchangeCarrots.new(attrs['value'].to_i), attrs['order'].to_i
|
150
|
+
)
|
132
151
|
when 'winner'
|
133
|
-
|
152
|
+
winning_player = parsePlayer(attrs)
|
153
|
+
@gamestate.condition = Condition.new(winning_player)
|
154
|
+
@context[:player] = winning_player
|
134
155
|
when 'left'
|
135
156
|
logger.debug 'got left event, terminating'
|
136
157
|
@network.disconnect
|
158
|
+
when 'lastNonSkipAction'
|
159
|
+
@context[:player].last_non_skip_action =
|
160
|
+
case attrs['class']
|
161
|
+
when 'advance'
|
162
|
+
Advance.new(attrs['distance'].to_i)
|
163
|
+
when 'card'
|
164
|
+
Card.new(CardType.find_by_key(attrs['type'].to_sym), attrs['value'].to_i)
|
165
|
+
when 'skip'
|
166
|
+
Skip.new
|
167
|
+
when 'eatSalad'
|
168
|
+
EatSalad.new
|
169
|
+
when 'fallBack'
|
170
|
+
FallBack.new
|
171
|
+
when 'exchangeCarrots'
|
172
|
+
ExchangeCarrots.new(attrs['value'].to_i)
|
173
|
+
else
|
174
|
+
raise "Unknown action type #{attrs['class']}"
|
175
|
+
end
|
137
176
|
end
|
138
177
|
end
|
139
178
|
|
@@ -147,13 +186,10 @@ class Protocol
|
|
147
186
|
attributes['displayName']
|
148
187
|
)
|
149
188
|
player.points = attributes['points'].to_i
|
150
|
-
player.
|
151
|
-
player.
|
152
|
-
player.
|
153
|
-
player.
|
154
|
-
player.coal = attributes['coal'].to_i
|
155
|
-
player.velocity = attributes['speed'].to_i
|
156
|
-
player.movement = player.velocity
|
189
|
+
player.index = attributes['index'].to_i
|
190
|
+
player.carrots = attributes['carrots'].to_i
|
191
|
+
player.salads = attributes['salads'].to_i
|
192
|
+
player.cards = []
|
157
193
|
player
|
158
194
|
end
|
159
195
|
|
@@ -171,6 +207,13 @@ class Protocol
|
|
171
207
|
@network.sendString("<room roomId=\"#{@roomId}\">#{string}</room>")
|
172
208
|
end
|
173
209
|
|
210
|
+
# converts "this_snake_case" to "thisSnakeCase"
|
211
|
+
def snake_case_to_lower_camel_case(string)
|
212
|
+
string.split('_').inject([]) do |result, e|
|
213
|
+
result + [result.empty? ? e : e.capitalize]
|
214
|
+
end.join
|
215
|
+
end
|
216
|
+
|
174
217
|
# Converts a move to XML for sending to the server.
|
175
218
|
#
|
176
219
|
# @param move [Move] The move to convert to XML.
|
@@ -183,20 +226,20 @@ class Protocol
|
|
183
226
|
# because XML-generation should be decoupled from internal data
|
184
227
|
# structures.
|
185
228
|
attribute = case action.type
|
186
|
-
when :acceleration
|
187
|
-
{ acc: action.acceleration }
|
188
|
-
when :push
|
189
|
-
{ direction: action.direction.key }
|
190
|
-
when :turn
|
191
|
-
{ direction: action.turn_steps }
|
192
229
|
when :advance
|
193
230
|
{ distance: action.distance }
|
194
|
-
when
|
231
|
+
when :skip, :eat_salad, :fall_back
|
232
|
+
{}
|
233
|
+
when :card
|
234
|
+
{ type: action.card_type.key.to_s, value: action.value }
|
235
|
+
when :exchange_carrots
|
236
|
+
{ value: action.value }
|
237
|
+
else
|
195
238
|
raise "unknown action type: #{action.type.inspect}. "\
|
196
239
|
"Can't convert to XML!"
|
197
240
|
end
|
198
241
|
attribute[:order] = index
|
199
|
-
data.tag!(action.type, attribute)
|
242
|
+
data.tag!(snake_case_to_lower_camel_case(action.type.to_s), attribute)
|
200
243
|
end
|
201
244
|
end
|
202
245
|
move.hints.each do |hint|
|