software_challenge_client 20.2.2 → 21.0.2

Sign up to get free protection for your applications and to get access to all the features.
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