software_challenge_client 20.2.1 → 21.0.1

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 (48) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +1 -0
  3. data/.ruby-version +1 -1
  4. data/.vscode/launch.json +41 -0
  5. data/.vscode/settings.json +10 -0
  6. data/Dockerfile +1 -1
  7. data/Gemfile +1 -0
  8. data/Guardfile +1 -0
  9. data/README.md +4 -3
  10. data/RELEASES.md +20 -0
  11. data/Rakefile +4 -4
  12. data/example/client.rb +6 -9
  13. data/example/main.rb +9 -9
  14. data/lib/software_challenge_client.rb +26 -23
  15. data/lib/software_challenge_client/board.rb +99 -34
  16. data/lib/software_challenge_client/client_interface.rb +1 -0
  17. data/lib/software_challenge_client/color.rb +16 -0
  18. data/lib/software_challenge_client/condition.rb +4 -1
  19. data/lib/software_challenge_client/coordinate_set.rb +92 -0
  20. data/lib/software_challenge_client/coordinates.rb +45 -0
  21. data/lib/software_challenge_client/debug_hint.rb +1 -0
  22. data/lib/software_challenge_client/field.rb +21 -53
  23. data/lib/software_challenge_client/game_rule_logic.rb +257 -328
  24. data/lib/software_challenge_client/game_state.rb +86 -68
  25. data/lib/software_challenge_client/has_hints.rb +1 -1
  26. data/lib/software_challenge_client/invalid_move_exception.rb +1 -0
  27. data/lib/software_challenge_client/logging.rb +1 -0
  28. data/lib/software_challenge_client/network.rb +1 -1
  29. data/lib/software_challenge_client/piece.rb +43 -14
  30. data/lib/software_challenge_client/piece_shape.rb +109 -0
  31. data/lib/software_challenge_client/player.rb +7 -6
  32. data/lib/software_challenge_client/player_type.rb +14 -0
  33. data/lib/software_challenge_client/protocol.rb +82 -76
  34. data/lib/software_challenge_client/rotation.rb +22 -0
  35. data/lib/software_challenge_client/runner.rb +2 -1
  36. data/lib/software_challenge_client/set_move.rb +13 -4
  37. data/lib/software_challenge_client/skip_move.rb +5 -0
  38. data/lib/software_challenge_client/util/constants.rb +3 -4
  39. data/lib/software_challenge_client/version.rb +2 -1
  40. data/lib/update_client_module.sh +15 -0
  41. data/software_challenge_client.gemspec +15 -13
  42. metadata +54 -36
  43. data/lib/software_challenge_client/cube_coordinates.rb +0 -23
  44. data/lib/software_challenge_client/direction.rb +0 -55
  45. data/lib/software_challenge_client/drag_move.rb +0 -19
  46. data/lib/software_challenge_client/line_direction.rb +0 -15
  47. data/lib/software_challenge_client/piece_type.rb +0 -18
  48. data/lib/software_challenge_client/player_color.rb +0 -25
@@ -1,4 +1,5 @@
1
1
  # encoding: UTF-8
2
+ # frozen_string_literal: true
2
3
 
3
4
  # Ein Spieler
4
5
  class Player
@@ -6,15 +7,15 @@ class Player
6
7
  # @return [String] der Name des Spielers, hat keine Auswirkungen auf das Spiel
7
8
  attr_reader :name
8
9
 
9
- # @!attribute [r] color
10
- # @return [PlayerColor] die Farbe des Spielers, Rot oder Blau
11
- attr_reader :color
10
+ # @!attribute [r] type
11
+ # @return [PlayerType] erster (PlayerType::ONE) oder zweiter (PlayerType::TWO) Spieler
12
+ attr_reader :type
12
13
 
13
14
  # Konstruktor
14
- # @param color [PlayerColor] Farbe
15
+ # @param type [PlayerType] Erster oder zweiter
15
16
  # @param name [String] Name
16
- def initialize(color, name)
17
- @color = color
17
+ def initialize(type, name)
18
+ @type = type
18
19
  @name = name
19
20
  end
20
21
 
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'typesafe_enum'
4
+
5
+ # Erster oder zweiter Spieler:
6
+ #
7
+ # ONE
8
+ # TWO
9
+ #
10
+ # Zugriff z.B. mit PlayerType::ONE
11
+ class PlayerType < TypesafeEnum::Base
12
+ new :ONE
13
+ new :TWO
14
+ end
@@ -3,7 +3,6 @@
3
3
  require 'socket'
4
4
  require_relative 'board'
5
5
  require_relative 'set_move'
6
- require_relative 'drag_move'
7
6
  require_relative 'skip_move'
8
7
  require_relative 'player'
9
8
  require_relative 'network'
@@ -41,7 +40,7 @@ class Protocol
41
40
  #
42
41
  # @param text [String] the xml-string that will be parsed
43
42
  def process_string(text)
44
- logger.debug "Parse XML:\n#{text}\n----END XML"
43
+ #logger.debug "Parse XML:\n#{text}\n----END XML"
45
44
  begin
46
45
  REXML::Document.parse_stream(text, self)
47
46
  rescue REXML::ParseException => e
@@ -50,7 +49,6 @@ class Protocol
50
49
  end
51
50
  end
52
51
 
53
-
54
52
  # called when text is encountered
55
53
  def text(text)
56
54
  @context[:last_text] = text
@@ -63,6 +61,33 @@ class Protocol
63
61
  case name
64
62
  when 'board'
65
63
  logger.debug @gamestate.board.to_s
64
+ when 'color'
65
+ if @context[:color] == :ordered_colors
66
+ @gamestate.ordered_colors << Color.to_a.find {|s| s.key == @context[:last_text].to_sym }
67
+ end
68
+ when 'shape'
69
+ case @context[:piece_target]
70
+ when :blue_shapes
71
+ last = @context[:last_text]
72
+ arr = PieceShape.to_a
73
+ shape = arr.find {|s| s.key == @context[:last_text].to_sym }
74
+ @gamestate.undeployed_blue_pieces << shape
75
+ when :yellow_shapes
76
+ shape = PieceShape.to_a.find {|s| s.key == @context[:last_text].to_sym }
77
+ @gamestate.undeployed_yellow_pieces << shape
78
+ when :red_shapes
79
+ shape = PieceShape.to_a.find {|s| s.key == @context[:last_text].to_sym }
80
+ @gamestate.undeployed_red_pieces << shape
81
+ when :green_shapes
82
+ shape = PieceShape.to_a.find {|s| s.key == @context[:last_text].to_sym }
83
+ @gamestate.undeployed_green_pieces << shape
84
+ end
85
+ when 'yellowShapes'
86
+
87
+ when 'redShapes'
88
+
89
+ when 'greenShapes'
90
+
66
91
  end
67
92
  end
68
93
 
@@ -97,59 +122,69 @@ class Protocol
97
122
  when 'state'
98
123
  logger.debug 'new gamestate'
99
124
  @gamestate = GameState.new
125
+ @gamestate.current_color_index = attrs['currentColorIndex'].to_i
100
126
  @gamestate.turn = attrs['turn'].to_i
101
- @gamestate.start_player_color = attrs['startPlayerColor'] == 'RED' ? PlayerColor::RED : PlayerColor::BLUE
102
- @gamestate.current_player_color = attrs['currentPlayerColor'] == 'RED' ? PlayerColor::RED : PlayerColor::BLUE
103
- logger.debug "Turn: #{@gamestate.turn}"
104
- when 'red'
105
- logger.debug 'new red player'
106
- player = parsePlayer(attrs)
107
- if player.color != PlayerColor::RED
108
- throw new IllegalArgumentException("expected #{PlayerColor::RED} Player but got #{player.color}")
109
- end
127
+ @gamestate.round = attrs['round'].to_i
128
+ @gamestate.start_piece = PieceShape.to_a.find {|s| s.key == attrs['startPiece'].to_sym }
129
+ logger.debug "Round: #{@gamestate.round}, Turn: #{@gamestate.turn}"
130
+ when 'first'
131
+ logger.debug 'new first player'
132
+ player = Player.new(PlayerType::ONE, attrs['displayName'])
110
133
  @gamestate.add_player(player)
111
134
  @context[:player] = player
112
- when 'blue'
113
- logger.debug 'new blue player'
114
- player = parsePlayer(attrs)
115
- if player.color != PlayerColor::BLUE
116
- throw new IllegalArgumentException("expected #{PlayerColor::BLUE} Player but got #{player.color}")
117
- end
135
+ @context[:color] = :one
136
+ when 'second'
137
+ logger.debug 'new second player'
138
+ player = Player.new(PlayerType::TWO, attrs['displayName'])
118
139
  @gamestate.add_player(player)
119
140
  @context[:player] = player
141
+ @context[:color] = :two
142
+ when 'orderedColors'
143
+ @context[:color] = :ordered_colors
144
+ @gamestate.ordered_colors = []
120
145
  when 'board'
121
146
  logger.debug 'new board'
122
- @gamestate.board = Board.new
147
+ @gamestate.board = Board.new()
123
148
  when 'field'
124
149
  x = attrs['x'].to_i
125
150
  y = attrs['y'].to_i
126
- obstructed = attrs['isObstructed'] == 'true'
127
- field = Field.new(x, y, [], obstructed)
151
+ color = Color.find_by_key(attrs['content'].to_sym)
152
+ field = Field.new(x, y, color)
128
153
  @gamestate.board.add_field(field)
129
154
  @context[:piece_target] = :field
130
155
  @context[:field] = field
156
+ when 'blueShapes'
157
+ @context[:piece_target] = :blue_shapes
158
+ @gamestate.undeployed_blue_pieces = []
159
+ when 'yellowShapes'
160
+ @context[:piece_target] = :yellow_shapes
161
+ @gamestate.undeployed_yellow_pieces = []
162
+ when 'redShapes'
163
+ @context[:piece_target] = :red_shapes
164
+ @gamestate.undeployed_red_pieces = []
165
+ when 'greenShapes'
166
+ @context[:piece_target] = :green_shapes
167
+ @gamestate.undeployed_green_pieces = []
131
168
  when 'piece'
132
- owner = PlayerColor.find_by_key(attrs['owner'].to_sym)
133
- type = PieceType.find_by_key(attrs['type'].to_sym)
134
- piece = Piece.new(owner, type)
169
+ color = Color.find_by_key(attrs['color'].to_sym)
170
+ kind = PieceShape.find_by_key(attrs['kind'].to_sym)
171
+ rotation = Rotation.find_by_key(attrs['rotation'].to_sym)
172
+ is_flipped = attrs['isFlipped'].downcase == "true"
173
+ piece = Piece.new(color, kind, rotation, is_flipped, Coordinates.origin)
135
174
  case @context[:piece_target]
136
- when :field
137
- @context[:field].add_piece(piece)
138
- when :undeployed_red_pieces
139
- @gamestate.undeployed_red_pieces << piece
140
- when :undeployed_blue_pieces
175
+ when :blue_shapes
141
176
  @gamestate.undeployed_blue_pieces << piece
177
+ when :yellow_shapes
178
+ @gamestate.undeployed_yellow_pieces << piece
179
+ when :red_shapes
180
+ @gamestate.green_red_pieces << piece
181
+ when :green_shapes
182
+ @gamestate.undeployed_green_pieces << piece
142
183
  when :last_move
143
184
  @context[:last_move_piece] = piece
144
185
  else
145
186
  raise "unknown piece target #{@context[:piece_target]}"
146
187
  end
147
- when 'undeployedRedPieces'
148
- @context[:piece_target] = :undeployed_red_pieces
149
- @gamestate.undeployed_red_pieces = []
150
- when 'undeployedBluePieces'
151
- @context[:piece_target] = :undeployed_blue_pieces
152
- @gamestate.undeployed_blue_pieces = []
153
188
  when 'lastMove'
154
189
  type = attrs['class']
155
190
  if type == 'skipmove'
@@ -158,24 +193,16 @@ class Protocol
158
193
  @context[:last_move_type] = type
159
194
  @context[:piece_target] = :last_move
160
195
  end
161
- when 'start'
162
- @context[:last_move_start] = CubeCoordinates.new(attrs['x'].to_i, attrs['y'].to_i, attrs['z'].to_i)
163
- when 'destination'
164
- destination = CubeCoordinates.new(attrs['x'].to_i, attrs['y'].to_i, attrs['z'].to_i)
165
- case @context[:last_move_type]
166
- when 'setmove'
167
- @gamestate.last_move = SetMove.new(@context[:last_move_piece], destination)
168
- when 'dragmove'
169
- @gamestate.last_move = SetMove.new(@context[:last_move_start], destination)
170
- end
196
+ when 'startColor'
197
+ @gamestate.start_color = Color::BLUE
171
198
  when 'winner'
172
- # TODO
173
- #winning_player = parsePlayer(attrs)
174
- #@gamestate.condition = Condition.new(winning_player, @gamestate.condition.reason)
199
+ # TODO
200
+ # winning_player = parsePlayer(attrs)
201
+ # @gamestate.condition = Condition.new(winning_player, @gamestate.condition.reason)
175
202
  when 'score'
176
203
  # TODO
177
204
  # there are two score tags in the result, but reason attribute should be equal on both
178
- #@gamestate.condition = Condition.new(@gamestate.condition.winner, attrs['reason'])
205
+ # @gamestate.condition = Condition.new(@gamestate.condition.winner, attrs['reason'])
179
206
  when 'left'
180
207
  logger.debug 'got left event, terminating'
181
208
  @network.disconnect
@@ -185,17 +212,6 @@ class Protocol
185
212
  end
186
213
  end
187
214
 
188
- # Converts XML attributes for a Player to a new Player object
189
- #
190
- # @param attributes [Hash] Attributes for the new Player.
191
- # @return [Player] The created Player object.
192
- def parsePlayer(attributes)
193
- Player.new(
194
- PlayerColor.find_by_key(attributes['color'].to_sym),
195
- attributes['displayName']
196
- )
197
- end
198
-
199
215
  # send a xml document
200
216
  #
201
217
  # @param document [REXML::Document] the document, that will be send to the connected server
@@ -205,7 +221,7 @@ class Protocol
205
221
 
206
222
  # send a string
207
223
  #
208
- # @param document [String] The string that will be send to the connected server.
224
+ # @param string [String] The string that will be send to the connected server.
209
225
  def sendString(string)
210
226
  @network.sendString("<room roomId=\"#{@roomId}\">#{string}</room>")
211
227
  end
@@ -228,26 +244,17 @@ class Protocol
228
244
  # structures.
229
245
  case move
230
246
  when SetMove
231
- builder.data(class: 'setmove') do |data|
232
- data.piece(owner: move.piece.owner.key, type: move.piece.type.key)
233
- d = move.destination
234
- data.destination(x: d.x, y: d.y, z: d.z)
235
- move.hints.each do |hint|
236
- data.hint(content: hint.content)
247
+ builder.data(class: 'sc.plugin2021.SetMove') do |data|
248
+ data.piece(color: move.piece.color, kind: move.piece.kind, rotation: move.piece.rotation, isFlipped: move.piece.is_flipped) do |piece|
249
+ piece.position(x: move.piece.position.x, y: move.piece.position.y)
237
250
  end
238
- end
239
- when DragMove
240
- builder.data(class: 'dragmove') do |data|
241
- s = move.start
242
- data.start(x: s.x, y: s.y, z: s.z)
243
- d = move.destination
244
- data.destination(x: d.x, y: d.y, z: d.z)
245
251
  move.hints.each do |hint|
246
252
  data.hint(content: hint.content)
247
253
  end
248
254
  end
249
255
  when SkipMove
250
- builder.data(class: 'skipmove') do |data|
256
+ builder.data(class: 'sc.plugin2021.SkipMove') do |data|
257
+ data.color(@gamestate.current_color.key.to_s)
251
258
  move.hints.each do |hint|
252
259
  data.hint(content: hint.content)
253
260
  end
@@ -255,5 +262,4 @@ class Protocol
255
262
  end
256
263
  builder.target!
257
264
  end
258
-
259
265
  end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'typesafe_enum'
4
+
5
+ # Die Drehung eines Steins
6
+ class Rotation < TypesafeEnum::Base
7
+ new :NONE, 0
8
+ new :RIGHT, 1
9
+ new :MIRROR, 2
10
+ new :LEFT, 3
11
+
12
+ # Summiere beide Rotationen auf.
13
+ # (Die resultierende Rotation hat den gleichen Effekt wie die beiden Rotationen einzeln).
14
+ def rotate(rotation)
15
+ Rotation.to_a[(value + rotation.value) % Rotation.size]
16
+ end
17
+
18
+ # Gibt den rotation namen zurück
19
+ def to_s
20
+ self.key.to_s
21
+ end
22
+ end
@@ -1,4 +1,5 @@
1
1
  # encoding: UTF-8
2
+ # frozen_string_literal: true
2
3
  require_relative 'board'
3
4
  require_relative 'client_interface'
4
5
  require_relative 'network'
@@ -8,7 +9,7 @@ class Runner
8
9
  include Logging
9
10
 
10
11
  def initialize(host, port, client, reservation = nil)
11
- logger.info 'Software Challenge 2019'
12
+ logger.info 'Software Challenge 2021'
12
13
  logger.info 'Ruby Client'
13
14
  logger.info "Host: #{host}"
14
15
  logger.info "Port: #{port}"
@@ -1,15 +1,24 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require_relative 'has_hints'
2
4
 
5
+ # Ein SetMove platziert einen Stein auf dem Spielbrett
3
6
  class SetMove
4
-
5
7
  include HasHints
6
8
 
7
9
  attr_reader :piece
8
- attr_reader :destination
9
10
 
10
- def initialize(piece, destination)
11
+ # Erstellt ein neuen leeren Legezug.
12
+ def initialize(piece)
11
13
  @piece = piece
12
- @destination = destination
13
14
  @hints = []
14
15
  end
16
+
17
+ def ==(other)
18
+ piece == other.piece
19
+ end
20
+
21
+ def to_s
22
+ "SetMove(#{piece}"
23
+ end
15
24
  end
@@ -1,7 +1,12 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require_relative 'has_hints'
4
+
5
+ # Ein SkipMove ziegt an, dass die aktuelle Farbe keinen Stein platzieren will
2
6
  class SkipMove
3
7
  include HasHints
4
8
 
9
+ # Erstellt ein neuen leeren Aussetzzug.
5
10
  def initialize
6
11
  @hints = []
7
12
  end
@@ -4,8 +4,7 @@
4
4
  # Konstanten zum aktuellen Spiel.
5
5
  module Constants
6
6
  ROUND_LIMIT = 30 # Rundenbegrenzung. Nach Ende der angegebenen Runde endet auch das Spiel.
7
- GAME_IDENTIFIER = 'swc_2020_hive' # Der Identifikator des Spiels. Für die Kommunikation mit dem Spielserver.
8
- STARTING_PIECES = 'QSSSGGBBAAA'
9
- BOARD_SIZE = 11
10
- SHIFT = ((BOARD_SIZE - 1) / 2)
7
+ GAME_IDENTIFIER = 'swc_2021_blokus' # Der Identifikator des Spiels. Für die Kommunikation mit dem Spielserver.
8
+ BOARD_SIZE = 20 # Seitenlänge des Spielbretts in Feldern
9
+ TOTAL_PIECE_SHAPES = 21
11
10
  end
@@ -1,4 +1,5 @@
1
1
  # encoding: UTF-8
2
+ # frozen_string_literal: true
2
3
  module SoftwareChallengeClient
3
- VERSION = "20.2.1"
4
+ VERSION = '21.0.1'
4
5
  end
@@ -0,0 +1,15 @@
1
+ #!/bin/bash
2
+ # This creates a entrypoint for the gem named 'software_challenge_client.rb'
3
+ # which includes all ruby files under lib. Making sure that everthing is
4
+ # included after some files were added or removed (in the process of updating
5
+ # the gem for a new game).
6
+ shopt -s globstar || exit 1
7
+ cd lib || exit 1
8
+ FILENAME='software_challenge_client.rb'
9
+
10
+ echo "# frozen_string_literal: true
11
+ module SoftwareChallengeClient" > $FILENAME
12
+ for file in software_challenge_client/**/*.rb; do
13
+ echo " require '${file}'"
14
+ done >> $FILENAME;
15
+ echo "end" >> $FILENAME
@@ -1,39 +1,41 @@
1
1
  # coding: utf-8
2
+ # frozen_string_literal: true
2
3
  lib = File.expand_path('../lib', __FILE__)
3
4
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
5
  require 'software_challenge_client/version'
5
6
 
6
7
  Gem::Specification.new do |spec|
7
- spec.name = "software_challenge_client"
8
+ spec.name = 'software_challenge_client'
8
9
  spec.version = SoftwareChallengeClient::VERSION
9
10
  spec.authors = File.readlines('AUTHORS').select { |l| l[' <'] }.map { |l| l.match(/^(.*) *</)[1] }
10
11
  spec.email = File.readlines('AUTHORS').select { |l| l[' <'] }.map { |l| l.match(/<(.*)>/)[1] }
11
12
 
12
- spec.summary = 'This gem provides functions to build a client for the coding competition Software-Challenge 2017.'
13
+ spec.summary = 'Provides functions to build a client for the coding competition Software-Challenge Germany.'
13
14
  spec.description = ''
14
15
  spec.homepage = 'http://www.software-challenge.de'
15
16
 
16
17
  spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
17
- spec.bindir = "exe"
18
+ spec.bindir = 'exe'
18
19
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
19
- spec.require_paths = ["lib"]
20
+ spec.require_paths = ['lib']
20
21
 
21
- spec.required_ruby_version = '>= 2.3'
22
- spec.add_dependency 'typesafe_enum'
22
+ spec.required_ruby_version = '>= 2.5.5'
23
23
  spec.add_dependency 'builder'
24
+ spec.add_dependency 'typesafe_enum'
24
25
 
25
26
  spec.add_development_dependency 'bundler', '>= 1.10'
26
- spec.add_development_dependency 'rake', '>= 10.0'
27
- spec.add_development_dependency 'yard', '>= 0.8'
28
- spec.add_development_dependency 'rspec'
29
27
  spec.add_development_dependency 'fuubar'
30
- spec.add_development_dependency 'rubocop'
31
- spec.add_development_dependency 'rubocop-rspec'
32
28
  spec.add_development_dependency 'guard'
33
29
  spec.add_development_dependency 'guard-rspec'
34
30
  spec.add_development_dependency 'guard-rubocop'
35
31
  spec.add_development_dependency 'pry'
36
- spec.add_development_dependency 'pry-rescue'
37
- spec.add_development_dependency 'pry-coolline'
38
32
  spec.add_development_dependency 'pry-byebug'
33
+ spec.add_development_dependency 'pry-coolline'
34
+ spec.add_development_dependency 'pry-rescue'
35
+ spec.add_development_dependency 'rake', '>= 10.0'
36
+ spec.add_development_dependency 'rspec'
37
+ spec.add_development_dependency 'rubocop'
38
+ spec.add_development_dependency 'rubocop-rspec'
39
+ spec.add_development_dependency 'solargraph'
40
+ spec.add_development_dependency 'yard', '>= 0.8'
39
41
  end