software_challenge_client 0.1.5 → 0.2.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.
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,16 @@
1
+ # encoding: UTF-8
2
+
3
+ # Exception indicating a move which was performed is not valid for the given
4
+ # state.
5
+ class InvalidMoveException < StandardError
6
+ def initialize(msg, move_or_action)
7
+ # This exception will be thrown by a move or by an individual action,
8
+ # depending where the rule violation was detected.
9
+ @move_or_action = move_or_action
10
+ super(msg)
11
+ end
12
+
13
+ def message
14
+ "#{super}: #{@move_or_action}"
15
+ end
16
+ end
@@ -0,0 +1,24 @@
1
+ require 'logger'
2
+
3
+ # This module provides a shared logger to all classes into which it is mixed.
4
+ # See http://stackoverflow.com/a/6768164/390808
5
+ #
6
+ # Usage:
7
+ #
8
+ # class MyClass
9
+ # include Logging
10
+ #
11
+ # def a_method(x)
12
+ # logger.debug "you provided #{x}"
13
+ # end
14
+ # end
15
+ module Logging
16
+ def logger
17
+ Logging.logger
18
+ end
19
+
20
+ # Global, memoized, lazy initialized instance of a logger
21
+ def self.logger
22
+ @logger ||= Logger.new(STDOUT)
23
+ end
24
+ end
@@ -1,59 +1,60 @@
1
1
  # encoding: UTF-8
2
2
  require_relative 'debug_hint'
3
+ require_relative 'action'
3
4
 
4
- # @author Ralf-Tobias Diekert
5
- # A move, that can be performed in twixt
5
+ # A move that can be performed in Mississippi Queen. A move consists of multiple
6
+ # actions in a specific order.
6
7
  class Move
7
- # @!attribute [r] x
8
- # @return [Integer] x-coordinate
9
- attr_reader :x
10
- # @!attribute [r] y
11
- # @return [Integer] y-coordinate
12
- attr_reader :y
8
+ # @!attribute [r] actions
9
+ #
10
+ # @return [Array<Action>] List of actions which should be performed in this
11
+ # move in the order determined by the array order.
12
+ attr_reader :actions
13
+
13
14
  # @!attribute [r] hints
14
15
  # @return [Array<DebugHint>] the move's hints
15
16
  attr_reader :hints
16
17
 
17
18
  # Initializer
18
19
  #
19
- # @param x [Integer] x-coordinate
20
- # @param y [Integer] y-coordinate
21
- def initialize(x, y)
22
- @x = x
23
- @y = y
24
- @hints = Array.new
20
+ def initialize
21
+ @actions = []
22
+ @hints = []
25
23
  end
26
24
 
27
- # @overload addHint(hint)
28
25
  # adds a hint to the move
29
26
  # @param hint [DebugHint] the added hint
30
- # @overload addHint(key, value)
31
- # adds a hint to the move
32
- # @param key the added hint's key
33
- # @param value the added hint's value
34
- # @overload addHint(string)
35
- # adds a hint to the move
36
- # @param hint [String] the added hint's content
37
- def addHint(hint)
38
- @hints.push(hint);
27
+ def add_hint(hint)
28
+ @hints.push(hint)
39
29
  end
40
30
 
41
- # adds a hint to the move
42
- def addHint(key, value)
43
- self.addHint(DebugHint.new(key, value))
31
+ def ==(other)
32
+ actions.size == other.actions.size &&
33
+ actions.zip(other.actions).map { |a, b| a == b }.all?
44
34
  end
45
35
 
46
- # adds a hint to the move
47
- def addHint(string)
48
- self.addHint(DebugHint.new(string))
36
+ def to_s
37
+ "Move: #{actions}"
49
38
  end
50
39
 
51
- def ==(another_move)
52
- return self.x == another_move.x && self.y == another_move.y
40
+ def add_action(action)
41
+ @actions << action
53
42
  end
54
43
 
55
- def to_s
56
- return "Move:(#{self.x},#{self.y})"
44
+ def add_action_with_order(action, index)
45
+ @actions[index] = action
57
46
  end
58
47
 
59
- end
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
57
+ end
58
+ @actions.each { |a| a.perform!(gamestate, current_player) }
59
+ end
60
+ end
@@ -1,14 +1,17 @@
1
1
  # encoding: UTF-8
2
2
  require 'socket'
3
+ require 'rexml/document'
4
+ require 'rexml/element'
5
+
3
6
  require_relative 'protocol'
4
7
  require_relative 'board'
5
8
  require_relative 'client_interface'
6
- require 'rexml/document'
7
- require 'rexml/element'
8
9
 
9
- # @author Ralf-Tobias Diekert
10
10
  # This class handles the socket connection to the server
11
11
  class Network
12
+
13
+ include Logging
14
+
12
15
  @socket
13
16
  @host
14
17
  @port
@@ -31,8 +34,6 @@ class Network
31
34
  @protocol = Protocol.new(self, @client)
32
35
  @reservationID = reservation || ''
33
36
  @receiveBuffer = ''
34
-
35
- puts '> Network/Socket created.'
36
37
  end
37
38
 
38
39
  # connects the client with a given server
@@ -40,6 +41,7 @@ class Network
40
41
  # @return [Boolean] true, if successfully connected to the server
41
42
  def connect
42
43
  @socket = TCPSocket.open(@host, @port)
44
+ logger.info 'Connection to server established.'
43
45
  @connected = true
44
46
 
45
47
  self.sendString('<protocol>')
@@ -52,7 +54,7 @@ class Network
52
54
  else
53
55
  document = REXML::Document.new
54
56
  element = REXML::Element.new('join')
55
- element.add_attribute('gameType', 'swc_2016_twixt')
57
+ element.add_attribute('gameType', 'swc_2017_mississippi_queen')
56
58
  document.add(element)
57
59
  self.sendXML(document)
58
60
  end
@@ -67,12 +69,12 @@ class Network
67
69
  @connected = false
68
70
  @socket.close
69
71
  end
70
- puts '> Disconnected.'
72
+ logger.info 'Connection to server closed.'
71
73
  end
72
74
 
73
75
  # reads from the socket until "</room>" is read
74
76
  def readString
75
- puts 'reading'
77
+ logger.debug 'reading'
76
78
  sockMsg = ''
77
79
  if(!@connected)
78
80
  return
@@ -80,8 +82,7 @@ class Network
80
82
 
81
83
  line =''
82
84
  char = ''
83
- while line!="</room>"
84
- char = @socket.getc
85
+ while line != "</room>" && !(char = @socket.getc).nil?
85
86
  line+=char
86
87
  if char=='\n' || char==' '
87
88
 
@@ -89,7 +90,7 @@ class Network
89
90
  end
90
91
  sockMsg += char
91
92
  end
92
- puts 'ended reading'
93
+ logger.debug 'ended reading'
93
94
  if sockMsg != ''
94
95
 
95
96
  @receiveBuffer.concat(sockMsg)
@@ -97,12 +98,10 @@ class Network
97
98
  # Remove <protocol> tag
98
99
  @receiveBuffer = @receiveBuffer.gsub('<protocol>', '')
99
100
 
100
- puts 'Receive:'
101
- puts ''
102
- #puts @receiveBuffer
101
+ logger.debug "Received XML from server: #{@receiveBuffer}"
103
102
 
104
103
  # Process text
105
- @protocol.processString('<msg>'+@receiveBuffer+'</msg>');
104
+ @protocol.process_string("<msg>#{@receiveBuffer}</msg>");
106
105
  self.emptyReceiveBuffer
107
106
  end
108
107
  return true
@@ -129,15 +128,13 @@ class Network
129
128
  def sendString(s)
130
129
  if(@connected)
131
130
  @socket.print(s);
132
- puts 'Send:'
133
- puts ''
134
- puts(s);
131
+ logger.debug "Sending: #{s}"
135
132
  end
136
133
  end
137
134
 
138
135
  # sends a xml Document to the buffer
139
136
  #
140
- # @param xml [REXML::Docuent] the Document, that will be sent
137
+ # @param xml [REXML::Document] the Document, that will be sent
141
138
  def sendXML(xml)
142
139
  text = ''
143
140
  xml.write(text)
@@ -1,25 +1,62 @@
1
1
  # encoding: UTF-8
2
- require_relative 'player_color'
3
2
 
4
- # @author Ralf-Tobias Diekert
5
- # A player, participating at a game
3
+ # A player, participating in a game
6
4
  class Player
5
+ # @!attribute [r] name
6
+ # @return [PlayerColor] the player's name
7
+ attr_reader :name
8
+
7
9
  # @!attribute [r] color
8
10
  # @return [PlayerColor] the player's color
9
11
  attr_reader :color
12
+
10
13
  # @!attribute [rw] points
11
14
  # @return [Integer] the player's points
12
15
  attr_accessor :points
13
16
 
17
+ # @!attribute [rw] velocity
18
+ # @return [Integer] the player's current velocity
19
+ attr_accessor :velocity
20
+
21
+ # @!attribute [rw] coal
22
+ # @return [Integer] the player's current coal supply
23
+ attr_accessor :coal
24
+
25
+ # @!attribute [rw] direction
26
+ # @return [Direction] the player's current direction
27
+ attr_accessor :direction
28
+
29
+ # @!attribute [rw] x
30
+ # @return [Direction] the player's current x-position
31
+ attr_accessor :x
32
+
33
+ # @!attribute [rw] y
34
+ # @return [Direction] the player's current y-position
35
+ attr_accessor :y
36
+
37
+ # @!attribute [rw] movement
38
+ # @return [Direction] the player's current movement points
39
+ attr_accessor :movement
40
+
41
+ # @!attribute [rw] passengers
42
+ # @return [Integer] how many passengers the player's has picked up
43
+ attr_accessor :passengers
44
+
14
45
  # Initializer
15
- # @param the new player's color
16
- def initialize(color)
46
+ # @param color [PlayerColor] the new player's color
47
+ # @param name [String] the new player's name (for displaying)
48
+ def initialize(color, name)
17
49
  @color = color
18
- self.points = 0
50
+ @name = name
51
+ @points = 0
52
+ @velocity = 1
53
+ @movement = 1
54
+ @coal = 6
55
+ @passengers = 0
56
+ @direction = Direction::RIGHT
19
57
  end
20
58
 
21
- def ==(another_player)
22
- return self.color == another_player.color
59
+ def ==(other)
60
+ color == other.color
23
61
  end
24
-
25
- end
62
+ end
@@ -1,10 +1,11 @@
1
1
  # encoding: UTF-8
2
- # @author Ralf-Tobias Diekert
3
- #player color constants
4
- module PlayerColor
5
- NONE = 1
6
- RED = 2
7
- BLUE = 4
2
+ # player color constants
3
+ require 'typesafe_enum'
4
+ class PlayerColor < TypesafeEnum::Base
5
+
6
+ new :NONE
7
+ new :RED
8
+ new :BLUE
8
9
 
9
10
  # Returns the opponents Color
10
11
  #
@@ -21,4 +22,4 @@ module PlayerColor
21
22
  return PlayerColor::NONE
22
23
  end
23
24
  end
24
- end
25
+ end
@@ -1,43 +1,46 @@
1
1
  # encoding: UTF-8
2
+ # frozen_string_literal: true
2
3
  require 'socket'
3
4
  require_relative 'board'
4
5
  require_relative 'move'
5
6
  require_relative 'player'
6
7
  require_relative 'network'
7
- require_relative 'connection'
8
8
  require_relative 'client_interface'
9
9
  require 'rexml/document'
10
10
  require 'rexml/streamlistener'
11
+ require 'builder'
11
12
 
12
- # @author Ralf-Tobias Diekert
13
- # This class handles the parsing of xml strings according to the network protocol of twixt
13
+ # This class handles communication to the server over the XML communication
14
+ # protocol. Messages from the server are parsed and moves are serialized and
15
+ # send back.
14
16
  class Protocol
17
+ include Logging
15
18
  include REXML::StreamListener
16
19
 
17
20
  # @!attribute [r] gamestate
18
21
  # @return [Gamestate] current gamestate
19
22
  attr_reader :gamestate
20
- # @!attribute [rw] roomID
23
+ # @!attribute [rw] roomId
21
24
  # @return [String] current room id
22
- attr_accessor :roomID
25
+ attr_accessor :roomId
23
26
  # @!attribute [r] client
24
27
  # @return [ClientInterface] current client
25
28
  attr_reader :client
26
- @network
27
29
 
28
30
  def initialize(network, client)
29
31
  @gamestate = GameState.new
30
- @network, @client = network, client
31
- self.client.gamestate = self.gamestate
32
+ @network = network
33
+ @client = client
34
+ @context = {} # for saving context when stream-parsing the XML
35
+ @client.gamestate = @gamestate
32
36
  end
33
37
 
34
38
  # starts xml-string parsing
35
39
  #
36
40
  # @param text [String] the xml-string that will be parsed
37
- def processString(text)
38
- list = self
39
- #puts "Parse XML:\n#{text}\n----END XML"
40
- REXML::Document.parse_stream(text, list)
41
+ def process_string(text)
42
+ logger.debug "Parse XML:\n#{text}\n----END XML"
43
+ REXML::Document.parse_stream(text, self)
41
44
  end
42
45
 
43
46
  # called if an end-tag is read
@@ -45,10 +48,10 @@ class Protocol
45
48
  # @param name [String] the end-tag name, that was read
46
49
  def tag_end(name)
47
50
  case name
48
- when "board"
49
- puts @gamestate.board.to_s
50
- when "condition"
51
- puts "Game ended"
51
+ when 'board'
52
+ logger.debug @gamestate.board.to_s
53
+ when 'condition'
54
+ logger.info 'Game ended'
52
55
  @network.disconnect
53
56
  end
54
57
  end
@@ -61,85 +64,91 @@ class Protocol
61
64
  # @param attrs [Dictionary<String, String>] Attributes attached to the tag
62
65
  def tag_start(name, attrs)
63
66
  case name
64
- when "room"
65
- @roomID = attrs['roomId']
66
- puts "roomId : "+@roomID
67
- when "data"
68
- puts "data(class) : "+attrs['class']
69
- if attrs['class'] == "sc.framework.plugins.protocol.MoveRequest"
70
- @client.gamestate = self.gamestate
67
+ when 'room'
68
+ @roomId = attrs['roomId']
69
+ logger.info 'roomId : ' + @roomId
70
+ when 'data'
71
+ logger.debug "data(class) : #{attrs['class']}"
72
+ if attrs['class'] == 'sc.framework.plugins.protocol.MoveRequest'
73
+ @client.gamestate = gamestate
71
74
  move = @client.getMove
72
- document = REXML::Document.new
73
- document.add_element('room',{'roomId' => @roomID})
74
- data = REXML::Element.new('data')
75
- data.add_attribute('class', 'move')
76
- data.add_attribute('x', move.x)
77
- data.add_attribute('y', move.y)
78
- document.root.add_element(data)
79
- for h in move.hints
80
- hint = REXML::Element.new('hint')
81
- hint.add_attribute('content', h.content)
82
- document.root.elements['data'].elements << hint
83
- end
84
- self.sendXml(document)
75
+ sendString(move_to_xml(move))
85
76
  end
86
- if attrs['class'] == "error"
87
- puts "Game ended - ERROR"
88
- puts attrs['message']
77
+ if attrs['class'] == 'error'
78
+ logger.info "Game ended - ERROR: #{attrs['message']}"
89
79
  @network.disconnect
90
80
  end
91
- when "state"
92
- puts 'new gamestate'
81
+ when 'state'
82
+ logger.debug 'new gamestate'
83
+ @gamestate = GameState.new
93
84
  @gamestate.turn = attrs['turn'].to_i
94
- @gamestate.startPlayerColor = attrs['startPlayer'] == 'RED' ? PlayerColor::RED : PlayerColor::BLUE
95
- @gamestate.currentPlayerColor = attrs['currentPlayer'] == 'RED' ? PlayerColor::RED : PlayerColor::BLUE
96
- puts "Turn: #{@gamestate.turn}"
97
- when "red"
98
- puts 'new red player'
99
- @gamestate.addPlayer(Player.new(attrs['color'] == 'RED' ? PlayerColor::RED : PlayerColor::BLUE))
100
- @gamestate.red.points = attrs['points'].to_i
101
- when "blue"
102
- puts 'new blue player'
103
- @gamestate.addPlayer(Player.new(attrs['color'] == 'RED' ? PlayerColor::RED : PlayerColor::BLUE))
104
- @gamestate.blue.points = attrs['points'].to_i
105
- when "board"
106
- puts 'new board'
107
- @gamestate.board = Board.new(true)
108
- when "field"
109
- type = FieldType::NORMAL
110
- ownerColor = PlayerColor::NONE
111
- case attrs['type']
112
- when 'SWAMP'
113
- type = FieldType::SWAMP
114
- when 'RED'
115
- type = FieldType::RED
116
- when 'BLUE'
117
- type = FieldType::BLUE
118
- when "winner"
119
- puts "Game ended"
120
- @network.disconnect
121
- end
122
-
123
- case attrs['owner']
124
- when 'RED'
125
- ownerColor = PlayerColor::RED
126
- when 'BLUE'
127
- ownerColor = PlayerColor::BLUE
128
- end
85
+ @gamestate.start_player_color = attrs['startPlayer'] == 'RED' ? PlayerColor::RED : PlayerColor::BLUE
86
+ @gamestate.current_player_color = attrs['currentPlayer'] == 'RED' ? PlayerColor::RED : PlayerColor::BLUE
87
+ @gamestate.additional_free_turn_after_push = attrs['freeTurn'] == 'true'
88
+ logger.debug "Turn: #{@gamestate.turn}"
89
+ when 'red'
90
+ logger.debug 'new red player'
91
+ @gamestate.add_player(parsePlayer(PlayerColor::RED, attrs))
92
+ when 'blue'
93
+ logger.debug 'new blue player'
94
+ @gamestate.add_player(parsePlayer(PlayerColor::BLUE, attrs))
95
+ when 'board'
96
+ logger.debug 'new board'
97
+ @gamestate.board = Board.new
98
+ @context[:current_tile_index] = nil
99
+ @context[:current_tile_direction] = nil
100
+ when 'tile'
101
+ @context[:current_tile_index] = attrs['index'].to_i
102
+ @context[:current_tile_direction] = attrs['direction'].to_i
103
+ when 'field'
104
+ type = FieldType.find_by_key(attrs['type'].to_sym)
105
+ raise "unexpected field type: #{attrs['type']}. Known types are #{FieldType.map { |t| t.key.to_s }}" if type.nil?
129
106
  x = attrs['x'].to_i
130
107
  y = attrs['y'].to_i
108
+ points = attrs['points'].to_i
109
+ index = @context[:current_tile_index]
110
+ direction = @context[:current_tile_direction]
131
111
 
132
- @gamestate.board.fields[x][y] = Field.new(type, x, y)
133
- @gamestate.board.fields[x][y].ownerColor = ownerColor
134
- when "connection"
135
- @gamestate.board.connections.push(Connection.new(attrs['x1'].to_i, attrs['y1'].to_i, attrs['x2'].to_i, attrs['y2'].to_i, attrs['owner']))
136
- when "lastMove"
137
- @gamestate.lastMove = Move.new(attrs['x'], attrs['y'])
138
- when "condition"
112
+ @gamestate.board.fields[[x, y]] = Field.new(type, x, y, index, direction, points)
113
+ when 'lastMove'
114
+ @gamestate.lastMove = Move.new
115
+ when 'acceleration'
116
+ @gamestate.lastMove.add_action_with_order(Acceleration.new(attrs['acc'].to_i), attrs['order'].to_i)
117
+ when 'advance'
118
+ @gamestate.lastMove.add_action_with_order(Advance.new(attrs['distance'].to_i), attrs['order'].to_i)
119
+ when 'turn'
120
+ @gamestate.lastMove.add_action_with_order(Turn.new(attrs['direction'].to_i), attrs['order'].to_i)
121
+ when 'push'
122
+ @gamestate.lastMove.add_action_with_order(Push.new(Direction.find_by_key(attrs['direction'].to_sym)), attrs['order'].to_i)
123
+ when 'condition'
139
124
  @gamestate.condition = Condition.new(attrs['winner'], attrs['reason'])
140
125
  end
141
126
  end
142
127
 
128
+ # Converts XML attributes for a Player to a new Player object
129
+ #
130
+ # @param expectedColor [PlayerColor] Color the player should have. Method will
131
+ # throw an exception when expectedColor and color in attributes don't match.
132
+ # @param attributes [Hash] Attributes for the new Player.
133
+ # @return [Player] The created Player object.
134
+ def parsePlayer(expectedColor, attributes)
135
+ player = Player.new(
136
+ PlayerColor.find_by_key(attributes['color'].to_sym),
137
+ attributes['displayName']
138
+ )
139
+ if player.color != expectedColor
140
+ throw new IllegalArgumentException("expected #{expectedColor} Player but got #{attributes['color']}")
141
+ end
142
+ player.points = attributes['points'].to_i
143
+ player.direction = Direction.find_by_key(attributes['direction'].to_sym)
144
+ player.x = attributes['x'].to_i
145
+ player.y = attributes['y'].to_i
146
+ player.passengers = attributes['passenger'].to_i
147
+ player.velocity = attributes['speed'].to_i
148
+ player.movement = player.velocity
149
+ player
150
+ end
151
+
143
152
  # send a xml document
144
153
  #
145
154
  # @param document [REXML::Document] the document, that will be send to the connected server
@@ -147,4 +156,43 @@ class Protocol
147
156
  @network.sendXML(document)
148
157
  end
149
158
 
159
+ # send a string
160
+ #
161
+ # @param document [String] The string that will be send to the connected server.
162
+ def sendString(string)
163
+ @network.sendString("<room roomId=\"#{@roomId}\">#{string}</room>")
164
+ end
165
+
166
+ # Converts a move to XML for sending to the server.
167
+ #
168
+ # @param move [Move] The move to convert to XML.
169
+ def move_to_xml(move)
170
+ builder = Builder::XmlMarkup.new(indent: 2)
171
+ builder.data(class: 'move') do |data|
172
+ move.actions.each_with_index do |action, index|
173
+ # Converting every action type here instead of requiring the Action
174
+ # class interface to supply a method which returns the action hash
175
+ # because XML-generation should be decoupled from internal data
176
+ # structures.
177
+ attribute = case action.type
178
+ when :acceleration
179
+ { acc: action.acceleration }
180
+ when :push, :turn
181
+ { direction: action.direction }
182
+ when :advance
183
+ { distance: action.distance }
184
+ when default
185
+ raise "unknown action type: #{action.type.inspect}. "\
186
+ "Can't convert to XML!"
187
+ end
188
+ attribute[:order] = index
189
+ data.tag!(action.type, attribute)
190
+ end
191
+ end
192
+ move.hints.each do |hint|
193
+ data.hint(content: hint.content)
194
+ end
195
+ builder.target!
196
+ end
197
+
150
198
  end