software_challenge_client 20.2.2 → 21.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 (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 -332
  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 +83 -75
  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,27 @@ 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
66
85
  end
67
86
  end
68
87
 
@@ -97,59 +116,69 @@ class Protocol
97
116
  when 'state'
98
117
  logger.debug 'new gamestate'
99
118
  @gamestate = GameState.new
119
+ @gamestate.current_color_index = attrs['currentColorIndex'].to_i
100
120
  @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
121
+ @gamestate.round = attrs['round'].to_i
122
+ @gamestate.start_piece = PieceShape.to_a.find {|s| s.key == attrs['startPiece'].to_sym }
123
+ logger.debug "Round: #{@gamestate.round}, Turn: #{@gamestate.turn}"
124
+ when 'first'
125
+ logger.debug 'new first player'
126
+ player = Player.new(PlayerType::ONE, attrs['displayName'])
110
127
  @gamestate.add_player(player)
111
128
  @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
129
+ @context[:color] = :one
130
+ when 'second'
131
+ logger.debug 'new second player'
132
+ player = Player.new(PlayerType::TWO, attrs['displayName'])
118
133
  @gamestate.add_player(player)
119
134
  @context[:player] = player
135
+ @context[:color] = :two
136
+ when 'orderedColors'
137
+ @context[:color] = :ordered_colors
138
+ @gamestate.ordered_colors = []
120
139
  when 'board'
121
140
  logger.debug 'new board'
122
- @gamestate.board = Board.new
141
+ @gamestate.board = Board.new()
123
142
  when 'field'
124
143
  x = attrs['x'].to_i
125
144
  y = attrs['y'].to_i
126
- obstructed = attrs['isObstructed'] == 'true'
127
- field = Field.new(x, y, [], obstructed)
145
+ color = Color.find_by_key(attrs['content'].to_sym)
146
+ field = Field.new(x, y, color)
128
147
  @gamestate.board.add_field(field)
129
148
  @context[:piece_target] = :field
130
149
  @context[:field] = field
150
+ when 'blueShapes'
151
+ @context[:piece_target] = :blue_shapes
152
+ @gamestate.undeployed_blue_pieces = []
153
+ when 'yellowShapes'
154
+ @context[:piece_target] = :yellow_shapes
155
+ @gamestate.undeployed_yellow_pieces = []
156
+ when 'redShapes'
157
+ @context[:piece_target] = :red_shapes
158
+ @gamestate.undeployed_red_pieces = []
159
+ when 'greenShapes'
160
+ @context[:piece_target] = :green_shapes
161
+ @gamestate.undeployed_green_pieces = []
131
162
  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)
163
+ color = Color.find_by_key(attrs['color'].to_sym)
164
+ kind = PieceShape.find_by_key(attrs['kind'].to_sym)
165
+ rotation = Rotation.find_by_key(attrs['rotation'].to_sym)
166
+ is_flipped = attrs['isFlipped'].downcase == "true"
167
+ piece = Piece.new(color, kind, rotation, is_flipped, Coordinates.origin)
135
168
  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
169
+ when :blue_shapes
141
170
  @gamestate.undeployed_blue_pieces << piece
171
+ when :yellow_shapes
172
+ @gamestate.undeployed_yellow_pieces << piece
173
+ when :red_shapes
174
+ @gamestate.green_red_pieces << piece
175
+ when :green_shapes
176
+ @gamestate.undeployed_green_pieces << piece
142
177
  when :last_move
143
178
  @context[:last_move_piece] = piece
144
179
  else
145
180
  raise "unknown piece target #{@context[:piece_target]}"
146
181
  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
182
  when 'lastMove'
154
183
  type = attrs['class']
155
184
  if type == 'skipmove'
@@ -158,24 +187,24 @@ class Protocol
158
187
  @context[:last_move_type] = type
159
188
  @context[:piece_target] = :last_move
160
189
  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)
190
+ when 'position'
191
+ case @context[:piece_target]
192
+ when :last_move
193
+ x = attrs['x'].to_i
194
+ y = attrs['y'].to_i
195
+ piece = @context[:last_move_piece]
196
+ @gamestate.last_move = SetMove.new(Piece.new(piece.color, piece.kind, piece.rotation, piece.is_flipped, Coordinates.new(x, y)))
170
197
  end
198
+ when 'startColor'
199
+ @gamestate.start_color = Color::BLUE
171
200
  when 'winner'
172
- # TODO
173
- #winning_player = parsePlayer(attrs)
174
- #@gamestate.condition = Condition.new(winning_player, @gamestate.condition.reason)
201
+ # TODO
202
+ # winning_player = parsePlayer(attrs)
203
+ # @gamestate.condition = Condition.new(winning_player, @gamestate.condition.reason)
175
204
  when 'score'
176
205
  # TODO
177
206
  # 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'])
207
+ # @gamestate.condition = Condition.new(@gamestate.condition.winner, attrs['reason'])
179
208
  when 'left'
180
209
  logger.debug 'got left event, terminating'
181
210
  @network.disconnect
@@ -185,17 +214,6 @@ class Protocol
185
214
  end
186
215
  end
187
216
 
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
217
  # send a xml document
200
218
  #
201
219
  # @param document [REXML::Document] the document, that will be send to the connected server
@@ -205,7 +223,7 @@ class Protocol
205
223
 
206
224
  # send a string
207
225
  #
208
- # @param document [String] The string that will be send to the connected server.
226
+ # @param string [String] The string that will be send to the connected server.
209
227
  def sendString(string)
210
228
  @network.sendString("<room roomId=\"#{@roomId}\">#{string}</room>")
211
229
  end
@@ -228,26 +246,17 @@ class Protocol
228
246
  # structures.
229
247
  case move
230
248
  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)
249
+ builder.data(class: 'sc.plugin2021.SetMove') do |data|
250
+ data.piece(color: move.piece.color, kind: move.piece.kind, rotation: move.piece.rotation, isFlipped: move.piece.is_flipped) do |piece|
251
+ piece.position(x: move.piece.position.x, y: move.piece.position.y)
237
252
  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
253
  move.hints.each do |hint|
246
254
  data.hint(content: hint.content)
247
255
  end
248
256
  end
249
257
  when SkipMove
250
- builder.data(class: 'skipmove') do |data|
258
+ builder.data(class: 'sc.plugin2021.SkipMove') do |data|
259
+ data.color(@gamestate.current_color.key.to_s)
251
260
  move.hints.each do |hint|
252
261
  data.hint(content: hint.content)
253
262
  end
@@ -255,5 +264,4 @@ class Protocol
255
264
  end
256
265
  builder.target!
257
266
  end
258
-
259
267
  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.2"
4
+ VERSION = '21.0.2'
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