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.
@@ -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] lastMove
30
- # @return [Move] the last move, that was made
31
- attr_accessor :lastMove
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] 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
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
- @free_acceleration = true
61
- @free_turn = true
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
- then red
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
- return blue
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 point will be calculated
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
- 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
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.x == other_player.x && field.y == other_player.y
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
- 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
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, current_player)
49
- # check if acceleration is only first action
50
- other_action_before = false
51
- @actions.each do |a|
52
- if a.type != :acceleration
53
- other_action_before = true
54
- elsif other_action_before
55
- raise InvalidMoveException.new('Beschleunigung muss am Anfang des Zuges geschehen.', self)
56
- end
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
- # A player, participating in a game
4
+ # Ein Spieler
4
5
  class Player
5
6
  # @!attribute [r] name
6
- # @return [String] the player's name
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] the player's color
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] the player's points
15
+ # @return [Integer] der aktuelle Punktestand des Spielers
15
16
  attr_accessor :points
16
17
 
17
- # @!attribute [rw] velocity
18
- # @return [Integer] the player's current velocity
19
- attr_accessor :velocity
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] coal
22
- # @return [Integer] the player's current coal supply
23
- attr_accessor :coal
23
+ # @!attribute [rw] carrots
24
+ # @return [Integer] die aktuelle Anzahl Karotten des Spielers
25
+ attr_accessor :carrots
24
26
 
25
- # @!attribute [rw] direction
26
- # @return [Direction] the player's current direction
27
- attr_accessor :direction
27
+ # @!attribute [rw] salads
28
+ # @return [Integer] die aktuelle Anzahl Salate des Spielers
29
+ attr_accessor :salads
28
30
 
29
- # @!attribute [rw] x
30
- # @return [Integer] the player's current x-position
31
- attr_accessor :x
31
+ # @!attribute [rw] cards
32
+ # @return [Array[CardType]] die noch nicht gespielten Karten
33
+ attr_accessor :cards
32
34
 
33
- # @!attribute [rw] y
34
- # @return [Integer] the player's current y-position
35
- attr_accessor :y
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] movement
38
- # @return [Direction] the player's current movement points
39
- attr_accessor :movement
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
- # @!attribute [rw] passengers
42
- # @return [Integer] how many passengers the player's has picked up
43
- attr_accessor :passengers
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
- @velocity = 1
53
- @movement = 1
54
- @coal = 6
55
- @passengers = 0
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.opponentColor(color)
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 'result'
54
- logger.info 'Got game result'
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 'tile'
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
- x = attrs['x'].to_i
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.lastMove = Move.new
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.lastMove.add_action_with_order(Advance.new(attrs['distance'].to_i), attrs['order'].to_i)
128
- when 'turn'
129
- @gamestate.lastMove.add_action_with_order(Turn.new(attrs['direction'].to_i), attrs['order'].to_i)
130
- when 'push'
131
- @gamestate.lastMove.add_action_with_order(Push.new(Direction.find_by_key(attrs['direction'].to_sym)), attrs['order'].to_i)
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
- @gamestate.condition = Condition.new(parsePlayer(attrs))
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.direction = Direction.find_by_key(attributes['direction'].to_sym)
151
- player.x = attributes['x'].to_i
152
- player.y = attributes['y'].to_i
153
- player.passengers = attributes['passenger'].to_i
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 default
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|