software_challenge_client 20.2.4 → 21.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 (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 -3
  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 +94 -37
  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 +2 -0
  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 -56
  23. data/lib/software_challenge_client/game_rule_logic.rb +258 -335
  24. data/lib/software_challenge_client/game_state.rb +106 -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 +71 -13
  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 +81 -74
  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 -25
  44. data/lib/software_challenge_client/direction.rb +0 -55
  45. data/lib/software_challenge_client/drag_move.rb +0 -22
  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] == :valid_colors
66
+ @gamestate.valid_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
 
@@ -98,58 +117,67 @@ class Protocol
98
117
  logger.debug 'new gamestate'
99
118
  @gamestate = GameState.new
100
119
  @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
120
+ @gamestate.round = attrs['round'].to_i
121
+ @gamestate.start_piece = PieceShape.to_a.find {|s| s.key == attrs['startPiece'].to_sym }
122
+ logger.debug "Round: #{@gamestate.round}, Turn: #{@gamestate.turn}"
123
+ when 'first'
124
+ logger.debug 'new first player'
125
+ player = Player.new(PlayerType::ONE, attrs['displayName'])
110
126
  @gamestate.add_player(player)
111
127
  @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
128
+ @context[:color] = :one
129
+ when 'second'
130
+ logger.debug 'new second player'
131
+ player = Player.new(PlayerType::TWO, attrs['displayName'])
118
132
  @gamestate.add_player(player)
119
133
  @context[:player] = player
134
+ @context[:color] = :two
135
+ when 'validColors'
136
+ @context[:color] = :valid_colors
137
+ @gamestate.valid_colors = []
120
138
  when 'board'
121
139
  logger.debug 'new board'
122
- @gamestate.board = Board.new
140
+ @gamestate.board = Board.new()
123
141
  when 'field'
124
142
  x = attrs['x'].to_i
125
143
  y = attrs['y'].to_i
126
- obstructed = attrs['isObstructed'] == 'true'
127
- field = Field.new(x, y, [], obstructed)
144
+ color = Color.find_by_key(attrs['content'].to_sym)
145
+ field = Field.new(x, y, color)
128
146
  @gamestate.board.add_field(field)
129
147
  @context[:piece_target] = :field
130
148
  @context[:field] = field
149
+ when 'blueShapes'
150
+ @context[:piece_target] = :blue_shapes
151
+ @gamestate.undeployed_blue_pieces = []
152
+ when 'yellowShapes'
153
+ @context[:piece_target] = :yellow_shapes
154
+ @gamestate.undeployed_yellow_pieces = []
155
+ when 'redShapes'
156
+ @context[:piece_target] = :red_shapes
157
+ @gamestate.undeployed_red_pieces = []
158
+ when 'greenShapes'
159
+ @context[:piece_target] = :green_shapes
160
+ @gamestate.undeployed_green_pieces = []
131
161
  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)
162
+ color = Color.find_by_key(attrs['color'].to_sym)
163
+ kind = PieceShape.find_by_key(attrs['kind'].to_sym)
164
+ rotation = Rotation.find_by_key(attrs['rotation'].to_sym)
165
+ is_flipped = attrs['isFlipped'].downcase == "true"
166
+ piece = Piece.new(color, kind, rotation, is_flipped, Coordinates.origin)
135
167
  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
168
+ when :blue_shapes
141
169
  @gamestate.undeployed_blue_pieces << piece
170
+ when :yellow_shapes
171
+ @gamestate.undeployed_yellow_pieces << piece
172
+ when :red_shapes
173
+ @gamestate.green_red_pieces << piece
174
+ when :green_shapes
175
+ @gamestate.undeployed_green_pieces << piece
142
176
  when :last_move
143
177
  @context[:last_move_piece] = piece
144
178
  else
145
179
  raise "unknown piece target #{@context[:piece_target]}"
146
180
  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
181
  when 'lastMove'
154
182
  type = attrs['class']
155
183
  if type == 'skipmove'
@@ -158,24 +186,24 @@ class Protocol
158
186
  @context[:last_move_type] = type
159
187
  @context[:piece_target] = :last_move
160
188
  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)
189
+ when 'position'
190
+ case @context[:piece_target]
191
+ when :last_move
192
+ x = attrs['x'].to_i
193
+ y = attrs['y'].to_i
194
+ piece = @context[:last_move_piece]
195
+ @gamestate.last_move = SetMove.new(Piece.new(piece.color, piece.kind, piece.rotation, piece.is_flipped, Coordinates.new(x, y)))
170
196
  end
197
+ when 'startColor'
198
+ @gamestate.start_color = Color::BLUE
171
199
  when 'winner'
172
- # TODO
173
- #winning_player = parsePlayer(attrs)
174
- #@gamestate.condition = Condition.new(winning_player, @gamestate.condition.reason)
200
+ # TODO
201
+ # winning_player = parsePlayer(attrs)
202
+ # @gamestate.condition = Condition.new(winning_player, @gamestate.condition.reason)
175
203
  when 'score'
176
204
  # TODO
177
205
  # 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'])
206
+ # @gamestate.condition = Condition.new(@gamestate.condition.winner, attrs['reason'])
179
207
  when 'left'
180
208
  logger.debug 'got left event, terminating'
181
209
  @network.disconnect
@@ -185,17 +213,6 @@ class Protocol
185
213
  end
186
214
  end
187
215
 
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
216
  # send a xml document
200
217
  #
201
218
  # @param document [REXML::Document] the document, that will be send to the connected server
@@ -228,26 +245,17 @@ class Protocol
228
245
  # structures.
229
246
  case move
230
247
  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)
248
+ builder.data(class: 'sc.plugin2021.SetMove') do |data|
249
+ data.piece(color: move.piece.color, kind: move.piece.kind, rotation: move.piece.rotation, isFlipped: move.piece.is_flipped) do |piece|
250
+ piece.position(x: move.piece.position.x, y: move.piece.position.y)
237
251
  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
252
  move.hints.each do |hint|
246
253
  data.hint(content: hint.content)
247
254
  end
248
255
  end
249
256
  when SkipMove
250
- builder.data(class: 'skipmove') do |data|
257
+ builder.data(class: 'sc.plugin2021.SkipMove') do |data|
258
+ data.color(@gamestate.current_color.key.to_s)
251
259
  move.hints.each do |hint|
252
260
  data.hint(content: hint.content)
253
261
  end
@@ -255,5 +263,4 @@ class Protocol
255
263
  end
256
264
  builder.target!
257
265
  end
258
-
259
266
  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' # Spielsteine, die jeder Spieler am Anfang des Spiels bekommt in textueller Repraesentation
9
- BOARD_SIZE = 11 # Durchmesser des Spielbretts in Feldern
10
- SHIFT = ((BOARD_SIZE - 1) / 2) # Wert, den man auf eine CubeCoordinate addieren muss, um einen positiven Index (fuer das Feld-Array) zu bekommen
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.4"
4
+ VERSION = '21.2.0'
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