software_challenge_client 22.1.0.1 → 23.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (52) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +15 -15
  3. data/.rspec +3 -3
  4. data/.rubocop.yml +11 -11
  5. data/.ruby-version +1 -1
  6. data/.stickler.yml +7 -7
  7. data/.vscode/launch.json +40 -40
  8. data/.vscode/settings.json +9 -9
  9. data/AUTHORS +6 -6
  10. data/CODE_OF_CONDUCT.md +13 -13
  11. data/Dockerfile +3 -3
  12. data/Gemfile +5 -5
  13. data/Guardfile +45 -45
  14. data/README.md +172 -147
  15. data/RELEASES.md +144 -140
  16. data/Rakefile +7 -7
  17. data/bin/console +15 -15
  18. data/bin/setup +7 -7
  19. data/develop.sh +3 -3
  20. data/example/client.rb +35 -35
  21. data/example/main.rb +42 -42
  22. data/example/start.bat +2 -2
  23. data/generate-authors.sh +19 -19
  24. data/lib/software_challenge_client/board.rb +149 -127
  25. data/lib/software_challenge_client/client_interface.rb +19 -19
  26. data/lib/software_challenge_client/condition.rb +27 -27
  27. data/lib/software_challenge_client/coordinates.rb +71 -45
  28. data/lib/software_challenge_client/debug_hint.rb +17 -17
  29. data/lib/software_challenge_client/direction.rb +41 -0
  30. data/lib/software_challenge_client/field.rb +70 -69
  31. data/lib/software_challenge_client/game_rule_logic.rb +206 -141
  32. data/lib/software_challenge_client/game_state.rb +57 -24
  33. data/lib/software_challenge_client/invalid_move_exception.rb +15 -15
  34. data/lib/software_challenge_client/logging.rb +26 -26
  35. data/lib/software_challenge_client/move.rb +37 -41
  36. data/lib/software_challenge_client/network.rb +126 -126
  37. data/lib/software_challenge_client/piece.rb +43 -81
  38. data/lib/software_challenge_client/player.rb +31 -31
  39. data/lib/software_challenge_client/protocol.rb +103 -54
  40. data/lib/software_challenge_client/runner.rb +36 -36
  41. data/lib/software_challenge_client/team.rb +23 -25
  42. data/lib/software_challenge_client/util/constants.rb +9 -9
  43. data/lib/software_challenge_client/version.rb +5 -5
  44. data/lib/software_challenge_client.rb +23 -25
  45. data/lib/update_client_module.sh +15 -15
  46. data/push_image_production.sh +12 -12
  47. data/release.sh +9 -9
  48. data/software_challenge_client.gemspec +41 -41
  49. metadata +3 -5
  50. data/lib/software_challenge_client/color.rb +0 -26
  51. data/lib/software_challenge_client/has_hints.rb +0 -11
  52. data/lib/software_challenge_client/piece_type.rb +0 -16
@@ -1,126 +1,126 @@
1
- # encoding: UTF-8
2
- # frozen_string_literal: false
3
-
4
- require 'socket'
5
- require 'rexml/document'
6
- require 'rexml/element'
7
-
8
- require_relative 'protocol'
9
- require_relative 'board'
10
- require_relative 'client_interface'
11
- require_relative 'util/constants'
12
-
13
- # This class handles the socket connection to the server
14
- class Network
15
- include Logging
16
- include Constants
17
-
18
- # @!attribute [r] connected
19
- # @return [Boolean] true, if the client is connected to a server
20
- attr_reader :connected
21
-
22
- def initialize(host, port, board, client, reservation = nil)
23
- @host = host
24
- @port = port
25
- @board = board
26
- @client = client
27
-
28
- @connected = false
29
- @protocol = Protocol.new(self, @client)
30
- @reservation_id = reservation || ''
31
- @receive_buffer = ''
32
- end
33
-
34
- # connects the client with a given server
35
- #
36
- # @return [Boolean] true, if successfully connected to the server
37
- def connect
38
- @socket = TCPSocket.open(@host, @port)
39
- logger.info 'Connection to server established.'
40
- @connected = true
41
-
42
- sendString('<protocol>')
43
- document = REXML::Document.new
44
- if @reservation_id != ''
45
- element = REXML::Element.new('joinPrepared')
46
- element.add_attribute('reservationCode', @reservation_id)
47
- else
48
- element = REXML::Element.new('join')
49
- element.add_attribute('gameType', GAME_IDENTIFIER)
50
- end
51
- document.add(element)
52
- sendXML(document)
53
- @connected
54
- end
55
-
56
- # disconnects the client from a server
57
- def disconnect
58
- if @connected
59
- sendString('</protocol>')
60
- @connected = false
61
- @socket.close
62
- end
63
- logger.info 'Connection to server closed.'
64
- end
65
-
66
- # reads from the socket until "</room>" is read
67
- def readString
68
- return false unless @connected
69
- sock_msg = ''
70
-
71
- line = ''
72
- @socket.each_char do |char|
73
- line += char
74
- sock_msg += char
75
- line = '' if ['\n', ' '].include? char
76
- break if ['</room>', '</protocol>'].include? line
77
- end
78
- if sock_msg != ''
79
- @receive_buffer.concat(sock_msg)
80
-
81
- # Remove <protocol> tag
82
- @receive_buffer = @receive_buffer.gsub('<protocol>', '')
83
- @receive_buffer = @receive_buffer.gsub('</protocol>', '')
84
-
85
- logger.debug "Received XML from server: #{@receive_buffer}"
86
-
87
- # Process text
88
- @protocol.process_string(@receive_buffer)
89
- emptyReceiveBuffer
90
- end
91
- true
92
- end
93
-
94
- # sends a xml Document to the buffer
95
- #
96
- # @param xml [REXML::Document] the Document, that will be sent
97
- def sendXML(xml)
98
- text = ''
99
- xml.write(text)
100
- sendString(text)
101
- end
102
-
103
- # processes an incomming message
104
- #
105
- # @return [Boolean] true, if the processing of a incomming message was successfull
106
- def processMessages
107
- return false unless @connected
108
- readString
109
- end
110
-
111
- # sends a string to the socket
112
- #
113
- # @param s [String] the message, to be sent
114
- def sendString(s)
115
- return unless @connected
116
- logger.debug "Sending: #{s}"
117
- @socket.print(s)
118
- end
119
-
120
- private
121
-
122
- # empties the receive buffer
123
- def emptyReceiveBuffer
124
- @receive_buffer = ''
125
- end
126
- end
1
+ # encoding: UTF-8
2
+ # frozen_string_literal: false
3
+
4
+ require 'socket'
5
+ require 'rexml/document'
6
+ require 'rexml/element'
7
+
8
+ require_relative 'protocol'
9
+ require_relative 'board'
10
+ require_relative 'client_interface'
11
+ require_relative 'util/constants'
12
+
13
+ # This class handles the socket connection to the server
14
+ class Network
15
+ include Logging
16
+ include Constants
17
+
18
+ # @!attribute [r] connected
19
+ # @return [Boolean] true, if the client is connected to a server
20
+ attr_reader :connected
21
+
22
+ def initialize(host, port, board, client, reservation = nil)
23
+ @host = host
24
+ @port = port
25
+ @board = board
26
+ @client = client
27
+
28
+ @connected = false
29
+ @protocol = Protocol.new(self, @client)
30
+ @reservation_id = reservation || ''
31
+ @receive_buffer = ''
32
+ end
33
+
34
+ # connects the client with a given server
35
+ #
36
+ # @return [Boolean] true, if successfully connected to the server
37
+ def connect
38
+ @socket = TCPSocket.open(@host, @port)
39
+ logger.info 'Connection to server established.'
40
+ @connected = true
41
+
42
+ sendString('<protocol>')
43
+ document = REXML::Document.new
44
+ if @reservation_id != ''
45
+ element = REXML::Element.new('joinPrepared')
46
+ element.add_attribute('reservationCode', @reservation_id)
47
+ else
48
+ element = REXML::Element.new('join')
49
+ element.add_attribute('gameType', GAME_IDENTIFIER)
50
+ end
51
+ document.add(element)
52
+ sendXML(document)
53
+ @connected
54
+ end
55
+
56
+ # disconnects the client from a server
57
+ def disconnect
58
+ if @connected
59
+ sendString('</protocol>')
60
+ @connected = false
61
+ @socket.close
62
+ end
63
+ logger.info 'Connection to server closed.'
64
+ end
65
+
66
+ # reads from the socket until "</room>" is read
67
+ def readString
68
+ return false unless @connected
69
+ sock_msg = ''
70
+
71
+ line = ''
72
+ @socket.each_char do |char|
73
+ line += char
74
+ sock_msg += char
75
+ line = '' if ['\n', ' '].include? char
76
+ break if ['</room>', '</protocol>'].include? line
77
+ end
78
+ if sock_msg != ''
79
+ @receive_buffer.concat(sock_msg)
80
+
81
+ # Remove <protocol> tag
82
+ @receive_buffer = @receive_buffer.gsub('<protocol>', '')
83
+ @receive_buffer = @receive_buffer.gsub('</protocol>', '')
84
+
85
+ logger.debug "Received XML from server: #{@receive_buffer}"
86
+
87
+ # Process text
88
+ @protocol.process_string(@receive_buffer)
89
+ emptyReceiveBuffer
90
+ end
91
+ true
92
+ end
93
+
94
+ # sends a xml Document to the buffer
95
+ #
96
+ # @param xml [REXML::Document] the Document, that will be sent
97
+ def sendXML(xml)
98
+ text = ''
99
+ xml.write(text)
100
+ sendString(text)
101
+ end
102
+
103
+ # processes an incomming message
104
+ #
105
+ # @return [Boolean] true, if the processing of a incomming message was successfull
106
+ def processMessages
107
+ return false unless @connected
108
+ readString
109
+ end
110
+
111
+ # sends a string to the socket
112
+ #
113
+ # @param s [String] the message, to be sent
114
+ def sendString(s)
115
+ return unless @connected
116
+ logger.debug "Sending: #{s}"
117
+ @socket.print(s)
118
+ end
119
+
120
+ private
121
+
122
+ # empties the receive buffer
123
+ def emptyReceiveBuffer
124
+ @receive_buffer = ''
125
+ end
126
+ end
@@ -1,81 +1,43 @@
1
- # frozen_string_literal: true
2
-
3
- # Ein Spielstein mit Ausrichtung, Koordinaten und Farbe
4
- class Piece
5
- include Constants
6
-
7
- # @!attribute [rw] Color
8
- # @return [Color]
9
- attr_accessor :color
10
-
11
- # @!attribute [r] Typ des Spielsteins
12
- # @return [PieceType]
13
- attr_reader :type
14
-
15
- # @!attribute [rw] Koordinaten
16
- # @return [Coordinates]
17
- attr_accessor :position
18
-
19
- # @!attribute [rw] tower_height
20
- # @return [Integer] Die Anzahl Spielsteine übereinander inklusive des obersten
21
- attr_accessor :height
22
-
23
- # Erstellt einen neuen Spielstein.
24
- def initialize(color, type, position = Coordinates.origin, height = 0)
25
- @color = color
26
- @type = type
27
- @position = position
28
- @height = height
29
- end
30
-
31
- # Berechnet die Koordinaten zu denen sich dieser Spielstein bewegen könnte.
32
- #
33
- # @return [Array<Coordinates>] Die Zielkoordinaten
34
- def target_coords
35
- xdir = 0
36
- if color == Color::RED
37
- xdir = 1
38
- else
39
- xdir = -1
40
- end
41
-
42
- case type
43
- when PieceType::Herzmuschel
44
- coords = [Coordinates.new(xdir,-1), Coordinates.new(xdir,1)]
45
- when PieceType::Moewe
46
- coords = [Coordinates.new(1,0), Coordinates.new(-1,0), Coordinates.new(0,1),
47
- Coordinates.new(0,-1)]
48
- when PieceType::Seestern
49
- coords = [Coordinates.new(xdir,0), Coordinates.new(1,1), Coordinates.new(-1,1),
50
- Coordinates.new(1,-1), Coordinates.new(-1,-1)]
51
- when PieceType::Robbe
52
- coords = [Coordinates.new(-1,2), Coordinates.new(1,2), Coordinates.new(-2,1),
53
- Coordinates.new(2,1), Coordinates.new(-1,-2), Coordinates.new(1,-2),
54
- Coordinates.new(-2,-1), Coordinates.new(2,-1)]
55
- end
56
-
57
- coords.map{ |x| x + position }.to_a
58
- coords.map{ |x| x + position }.select{ |c| c.x >= 0 && c.y >=0 && c.x < BOARD_SIZE && c.y < BOARD_SIZE}.to_a
59
- end
60
-
61
- def ==(other)
62
- !other.nil? &&
63
- color == other.color &&
64
- position == other.position &&
65
- type == other.type
66
- end
67
-
68
- # @return [String] Gibt die String-Repräsentation zurück
69
- def to_s
70
- "#{color.key} #{type.key} at #{position}"
71
- end
72
-
73
- # @return [String] Gibt eine Kurzfassung der String-Repräsentation zurück
74
- def to_ss
75
- "#{color.key.to_s[0]}#{type.key.to_s[0]}"
76
- end
77
-
78
- def inspect
79
- to_s
80
- end
81
- end
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'direction'
4
+
5
+ # Ein Spielstein mit Ausrichtung, Koordinaten und Farbe
6
+ class Piece
7
+ include Constants
8
+
9
+ # @!attribute [rw] Team
10
+ # @return [Team]
11
+ attr_accessor :team
12
+
13
+ # @!attribute [rw] Koordinaten
14
+ # @return [Coordinates]
15
+ attr_accessor :coords
16
+
17
+ # Erstellt einen neuen Spielstein.
18
+ def initialize(team, coords = Coordinates.origin)
19
+ @team = team
20
+ @coords = coords
21
+ end
22
+
23
+ def ==(other)
24
+ !other.nil? &&
25
+ team == other.team &&
26
+ coords == other.coords
27
+ end
28
+
29
+ # @return [String] Gibt die String-Repräsentation zurück
30
+ def to_s
31
+ "#{team.key} at #{coords}"
32
+ end
33
+
34
+ # To short string
35
+ # @return [String] Gibt eine Kurzfassung der String-Repräsentation zurück
36
+ def to_ss
37
+ "#{team.key.to_s[0]}"
38
+ end
39
+
40
+ def inspect
41
+ to_s
42
+ end
43
+ end
@@ -1,31 +1,31 @@
1
- # encoding: UTF-8
2
- # frozen_string_literal: true
3
-
4
- # Ein Spieler
5
- class Player
6
- # @!attribute [r] name
7
- # @return [String] der Name des Spielers, hat keine Auswirkungen auf das Spiel
8
- attr_reader :name
9
-
10
- # @!attribute [r] color
11
- # @return [Color] erster (Color::RED) oder zweiter (Color::BLUE) Spieler
12
- attr_reader :color
13
-
14
- # @!attribute [rw] amber
15
- # @return [Integer] Anzahl Bernsteine die dieser Spieler gesammelt hat
16
- attr_accessor :amber
17
-
18
- # Konstruktor
19
- # @param type [Color] Rot oder blau
20
- # @param name [String] Name
21
- # @param amber [Integer] Menge des Bernsteins die der Spieler hat
22
- def initialize(color, name, amber = 0)
23
- @color = color
24
- @name = name
25
- @amber = amber
26
- end
27
-
28
- def ==(other)
29
- color == other.color
30
- end
31
- end
1
+ # encoding: UTF-8
2
+ # frozen_string_literal: true
3
+
4
+ # Ein Spieler
5
+ class Player
6
+ # @!attribute [r] name
7
+ # @return [String] der Name des Spielers, hat keine Auswirkungen auf das Spiel
8
+ attr_reader :name
9
+
10
+ # @!attribute [r] team
11
+ # @return [Team] erster (Team::ONE) oder zweiter (Team::TWO) Spieler
12
+ attr_reader :team
13
+
14
+ # @!attribute [rw] fishes
15
+ # @return [Integer] Anzahl Fische die dieser Spieler gesammelt hat
16
+ attr_accessor :fishes
17
+
18
+ # Konstruktor
19
+ # @param type [Team] One oder Two
20
+ # @param name [String] Name
21
+ # @param amber [Integer] Menge der Fische die der Spieler hat
22
+ def initialize(team, name, fishes = 0)
23
+ @team = team
24
+ @name = name
25
+ @fishes = fishes
26
+ end
27
+
28
+ def ==(other)
29
+ team == other.team
30
+ end
31
+ end
@@ -3,7 +3,6 @@
3
3
  require 'socket'
4
4
  require_relative 'board'
5
5
  require_relative 'move'
6
- require_relative 'piece_type'
7
6
  require_relative 'player'
8
7
  require_relative 'network'
9
8
  require_relative 'client_interface'
@@ -28,12 +27,25 @@ class Protocol
28
27
  # @return [ClientInterface] current client
29
28
  attr_reader :client
30
29
 
30
+ # @!attribute [rw] x
31
+ # @return [Integer] x
32
+ attr_reader :x
33
+ # @!attribute [rw] y
34
+ # @return [Integer] y
35
+ attr_reader :y
36
+ # @!attribute [rw] i
37
+ # @return [Integer] i
38
+ attr_reader :i
39
+
31
40
  def initialize(network, client)
32
41
  @gamestate = GameState.new
33
42
  @network = network
34
43
  @client = client
35
44
  @context = {} # for saving context when stream-parsing the XML
36
45
  @client.gamestate = @gamestate
46
+ @x = 0
47
+ @y = 0
48
+ @i = 0
37
49
  end
38
50
 
39
51
  # starts xml-string parsing
@@ -42,7 +54,7 @@ class Protocol
42
54
  def process_string(text)
43
55
  #logger.debug "Parse XML:\n#{text}\n----END XML"
44
56
  begin
45
- REXML::Document.parse_stream(text, self)
57
+ REXML::Document.parse_stream(text.encode('UTF-8', :invalid => :replace, :undef => :replace), self)
46
58
  rescue REXML::ParseException => e
47
59
  # to parse incomplete xml, ignore missing end tag exceptions
48
60
  raise e unless e.message =~ /Missing end tag/
@@ -54,34 +66,6 @@ class Protocol
54
66
  @context[:last_text] = text
55
67
  end
56
68
 
57
- # called if an end-tag is read
58
- #
59
- # @param name [String] the end-tag name, that was read
60
- def tag_end(name)
61
- case name
62
- when 'board'
63
- logger.debug @gamestate.board.to_s
64
- when 'startTeam'
65
- @gamestate.add_player(Player.new(Color::RED, "ONE", 0))
66
- @gamestate.add_player(Player.new(Color::BLUE, "TWO", 0))
67
- if @context[:last_text] == "ONE"
68
- @gamestate.start_team = @gamestate.player_one
69
- else
70
- @gamestate.start_team = @gamestate.player_two
71
- end
72
- when 'team'
73
- @context[:team] = @context[:last_text]
74
- when 'int'
75
- if @context[:team] == "ONE"
76
- logger.info 'Got player one amber'
77
- @gamestate.player_one.amber = @context[:last_text].to_i
78
- else
79
- logger.info 'Got player two amber'
80
- @gamestate.player_two.amber = @context[:last_text].to_i
81
- end
82
- end
83
- end
84
-
85
69
  # called if a start tag is read
86
70
  # Depending on the tag the gamestate is updated
87
71
  # or the client will be asked for a move
@@ -98,6 +82,14 @@ class Protocol
98
82
  @context[:data_class] = attrs['class']
99
83
  if attrs['class'] == 'moveRequest'
100
84
  @client.gamestate = gamestate
85
+ if gamestate.turn == 0
86
+ gamestate.myself_player = gamestate.start_player
87
+ logger.debug "I am #{gamestate.myself_player}"
88
+ elsif gamestate.turn == 1
89
+ gamestate.myself_player = gamestate.not_player(gamestate.start_player)
90
+ logger.debug "I am #{gamestate.myself_player}"
91
+ end
92
+ gamestate.current_player = gamestate.myself_player
101
93
  move = @client.move_requested
102
94
  sendString(move_to_xml(move))
103
95
  end
@@ -112,32 +104,35 @@ class Protocol
112
104
  end
113
105
  when 'state'
114
106
  logger.debug 'new gamestate'
115
- @gamestate = GameState.new
107
+ #@gamestate = GameState.new
116
108
  @gamestate.turn = attrs['turn'].to_i
117
109
  logger.debug "Round: #{@gamestate.round}, Turn: #{@gamestate.turn}"
118
110
  when 'board'
111
+ @x = 0
112
+ @y = 0
113
+ @i = 0
119
114
  logger.debug 'new board'
120
115
  @gamestate.board = Board.new()
121
- when 'pieces'
122
- @context[:entry] = :pieces
123
- when 'coordinates'
124
- @context[:x] = attrs['x'].to_i
125
- @context[:y] = attrs['y'].to_i
126
- when 'piece'
127
- x = @context[:x]
128
- y = @context[:y]
129
- team = Team.find_by_key(attrs['team'].to_sym)
130
- type = PieceType.find_by_key(attrs['type'].to_sym)
131
- count = attrs['count'].to_i
132
- field = Field.new(x, y, Piece.new(team.to_c, type, Coordinates.new(x, y), count))
133
- @gamestate.board.add_field(field)
134
- when 'from'
135
- @context[:from] = Coordinates.new(attrs['x'].to_i, attrs['y'].to_i)
136
- when 'to'
137
- from = @context[:from]
138
- @gamestate.last_move = Move.new(Coordinates.new(from.x, from.y), Coordinates.new(attrs['x'].to_i, attrs['y'].to_i))
139
- when 'ambers'
140
- @context[:entry] = :ambers
116
+ # when 'pieces'
117
+ # @context[:entry] = :pieces
118
+ # when 'coordinates'
119
+ # @context[:x] = attrs['x'].to_i
120
+ # @context[:y] = attrs['y'].to_i
121
+ # when 'piece'
122
+ # x = @context[:x]
123
+ # y = @context[:y]
124
+ # team = Team.find_by_key(attrs['team'].to_sym)
125
+ # type = PieceType.find_by_key(attrs['type'].to_sym)
126
+ # count = attrs['count'].to_i
127
+ # field = Field.new(x, y, Piece.new(team.to_c, type, Coordinates.new(x, y), count))
128
+ # @gamestate.board.add_field(field)
129
+ # when 'from'
130
+ # @context[:from] = Coordinates.new(attrs['x'].to_i, attrs['y'].to_i)
131
+ # when 'to'
132
+ # from = @context[:from]
133
+ # @gamestate.last_move = Move.new(Coordinates.new(from.x, from.y), Coordinates.new(attrs['x'].to_i, attrs['y'].to_i))
134
+ # when 'ambers'
135
+ # @context[:entry] = :ambers
141
136
  when 'winner'
142
137
  # TODO
143
138
  # winning_player = parsePlayer(attrs)
@@ -155,6 +150,50 @@ class Protocol
155
150
  end
156
151
  end
157
152
 
153
+ # called if an end-tag is read
154
+ #
155
+ # @param name [String] the end-tag name, that was read
156
+ def tag_end(name)
157
+ case name
158
+ when 'board'
159
+ logger.debug @gamestate.board.to_s
160
+ when 'startTeam'
161
+ @gamestate.add_player(Player.new(Team::ONE, "ONE", 0))
162
+ @gamestate.add_player(Player.new(Team::TWO, "TWO", 0))
163
+ if @context[:last_text] == "ONE"
164
+ @gamestate.start_player = @gamestate.player_one
165
+ else
166
+ @gamestate.start_player = @gamestate.player_two
167
+ end
168
+ when 'int'
169
+ @i += 1
170
+ if i == 1
171
+ logger.info 'Got player one fishes'
172
+ @gamestate.player_one.fishes = @context[:last_text].to_i
173
+ elsif i == 2
174
+ logger.info 'Got player two fishes'
175
+ @gamestate.player_two.fishes = @context[:last_text].to_i
176
+ else
177
+ logger.info 'We got a problemo'
178
+ end
179
+ when 'list'
180
+ @y += 1
181
+ @x = 0
182
+ when 'field'
183
+ if @context[:last_text] == "ONE"
184
+ field = Field.new(@x, @y, Piece.new(Team::ONE, Coordinates.new(@x, @y)))
185
+ elsif @context[:last_text] == "TWO"
186
+ field = Field.new(@x, @y, Piece.new(Team::TWO, Coordinates.new(@x, @y)))
187
+ else
188
+ field = Field.new(@x, @y, nil, @context[:last_text].to_i)
189
+ end
190
+ @gamestate.board.add_field(field)
191
+ @x += 1
192
+ else
193
+ why = 'tho'
194
+ end
195
+ end
196
+
158
197
  # send a xml document
159
198
  #
160
199
  # @param document [REXML::Document] the document, that will be send to the connected server
@@ -191,9 +230,19 @@ class Protocol
191
230
  # because XML-generation should be decoupled from internal data
192
231
  # structures.
193
232
 
194
- builder.data(class: 'move') do |d|
195
- d.from(x: move.from.x, y: move.from.y)
196
- d.to(x: move.to.x, y: move.to.y)
233
+ to_d = Coordinates.oddr_to_doubled(move.to)
234
+
235
+ if move.from.nil?
236
+ builder.data(class: 'move') do |d|
237
+ d.to(x: to_d.x, y: to_d.y)
238
+ end
239
+ else
240
+ from_d = Coordinates.oddr_to_doubled(move.from)
241
+
242
+ builder.data(class: 'move') do |d|
243
+ d.from(x: from_d.x, y: from_d.y)
244
+ d.to(x: to_d.x, y: to_d.y)
245
+ end
197
246
  end
198
247
 
199
248
  builder.target!