software_challenge_client 22.1.0.1 → 23.0.2

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 (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!