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,3 @@
1
- # encoding: utf-8
2
1
  # frozen_string_literal: true
3
2
 
4
3
  require_relative './util/constants'
@@ -15,32 +14,49 @@ class GameState
15
14
  # @!attribute [rw] turn
16
15
  # @return [Integer] Aktuelle Zugnummer (von 0 beginnend)
17
16
  attr_accessor :turn
18
- # @!attribute [rw] start_player_color
19
- # @return [PlayerColor] Die Farbe des Spielers, der den ersten Zug im Spiel
20
- # machen darf.
21
- attr_accessor :start_player_color
22
- # @!attribute [rw] current_player_color
23
- # @return [PlayerColor] Die Farbe des Spielers, der den nächsten Zug machen
24
- # darf, der also gerade an der Reihe ist.
25
- attr_accessor :current_player_color
17
+ # @!attribute [rw] round
18
+ # @return [Integer] Aktuelle Rundennummer (von 1 beginnend)
19
+ attr_accessor :round
20
+
21
+ # @!attribute [rw] startColor
22
+ # @return [Color] Die Farbe, die zuerst legen darf
23
+ attr_accessor :start_color
24
+ # @!attribute [rw] current_color_index
25
+ # @return [Color] Der jetzige Index in der Zug Reihenfolge der Farben.
26
+ attr_accessor :current_color_index
27
+ # @!attribute [rw] ordered_colors
28
+ # @return [Array<Color>] Ein Array aller Farben die ziehen können in
29
+ # der Reihenfolge in der sie drankommen
30
+ attr_accessor :ordered_colors
31
+
32
+ # @!attribute [r] undeployed_blue_pieces
33
+ # @return [Array<PieceShape>] Die blauen, nicht gesetzten Spielsteine
34
+ attr_accessor :undeployed_blue_pieces
35
+
36
+ # @!attribute [r] undeployed_yellow_pieces
37
+ # @return [Array<PieceShape>] Die gelben, nicht gesetzten Spielsteine
38
+ attr_accessor :undeployed_yellow_pieces
26
39
 
27
40
  # @!attribute [r] undeployed_red_pieces
28
- # @return [Player] Die nicht gesetzten Spielsteine des roten Spielers
41
+ # @return [Array<PieceShape>] Die roten, nicht gesetzten Spielsteine
29
42
  attr_accessor :undeployed_red_pieces
30
43
 
31
- # @!attribute [r] undeployed_blue_pieces
32
- # @return [Player] Die nicht gesetzten Spielsteine des roten Spielers
33
- attr_accessor :undeployed_blue_pieces
44
+ # @!attribute [r] undeployed_green_pieces
45
+ # @return [Array<PieceShape>] Die grünen, nicht gesetzten Spielsteine
46
+ attr_accessor :undeployed_green_pieces
34
47
 
35
- # @!attribute [r] red
36
- # @return [Player] Der rote Spieler
37
- attr_reader :red
38
- # @!attribute [r] blue
39
- # @return [Player] Der blaue Spieler
40
- attr_reader :blue
48
+ # @!attribute [r] player_one
49
+ # @return [Player] Der erste Spieler
50
+ attr_reader :player_one
51
+ # @!attribute [r] player_two
52
+ # @return [Player] Der zweite Spieler
53
+ attr_reader :player_two
41
54
  # @!attribute [rw] board
42
55
  # @return [Board] Das aktuelle Spielbrett
43
56
  attr_accessor :board
57
+ # @!attribute [rw] startPiece
58
+ # @return [PieceShape] Der Stein, der im ersten Zug von allen Farben gelegt werden muss
59
+ attr_accessor :start_piece
44
60
  # @!attribute [rw] last_move
45
61
  # @return [Move] Der zuletzt gemachte Zug (ist nil vor dem ersten Zug, also
46
62
  # bei turn == 0)
@@ -54,78 +70,75 @@ class GameState
54
70
  def field(x, y)
55
71
  board.field(x, y)
56
72
  end
57
- def self.parse_pieces_string(string, color)
58
- string.chars.map do |c|
59
- case c
60
- when 'Q'
61
- Piece.new(color, PieceType::BEE)
62
- when 'S'
63
- Piece.new(color, PieceType::SPIDER)
64
- when 'G'
65
- Piece.new(color, PieceType::GRASSHOPPER)
66
- when 'B'
67
- Piece.new(color, PieceType::BEETLE)
68
- when 'A'
69
- Piece.new(color, PieceType::ANT)
70
- end
71
- end
72
- end
73
73
 
74
+ # Erstellt einen neuen leeren Spielstand.
74
75
  def initialize
75
- @current_player_color = PlayerColor::RED
76
- @start_player_color = PlayerColor::RED
76
+ @current_color = Color::RED
77
+ @start_color = Color::RED
77
78
  @board = Board.new
78
79
  @turn = 0
79
- @undeployed_red_pieces = GameState.parse_pieces_string(Constants::STARTING_PIECES, PlayerColor::RED)
80
- @undeployed_blue_pieces = GameState.parse_pieces_string(Constants::STARTING_PIECES, PlayerColor::BLUE)
80
+ @undeployed_blue_pieces = PieceShape.to_a
81
+ @undeployed_yellow_pieces = PieceShape.to_a
82
+ @undeployed_red_pieces = PieceShape.to_a
83
+ @undeployed_green_pieces = PieceShape.to_a
84
+ @start_piece = GameRuleLogic.get_random_pentomino
81
85
  end
82
86
 
83
87
  # Fügt einen Spieler zum Spielzustand hinzu.
84
88
  #
85
89
  # @param player [Player] Der hinzuzufügende Spieler.
86
90
  def add_player(player)
87
- if player.color == PlayerColor::RED
88
- @red = player
89
- elsif player.color == PlayerColor::BLUE
90
- @blue = player
91
+ if player.type == PlayerType::ONE
92
+ @player_one = player
93
+ elsif player.type == PlayerType::TWO
94
+ @player_two = player
91
95
  end
92
96
  end
93
97
 
94
98
  # @return [Player] Spieler, der gerade an der Reihe ist.
95
99
  def current_player
96
- return red if current_player_color == PlayerColor::RED
97
- return blue if current_player_color == PlayerColor::BLUE
100
+ turn % 2 == 0 ? player_one : player_two
98
101
  end
99
102
 
100
103
  # @return [Player] Spieler, der gerade nicht an der Reihe ist.
101
104
  def other_player
102
- return blue if current_player_color == PlayerColor::RED
103
- return red if current_player_color == PlayerColor::BLUE
105
+ turn % 2 == 0 ? player_two : player_one
104
106
  end
105
107
 
106
- # @return [PlayerColor] Farbe des Spielers, der gerade nicht an der Reihe ist.
107
- def other_player_color
108
- PlayerColor.opponent_color(current_player_color)
108
+ # @return [PlayerType] Typ des Spielers, der gerade nicht an der Reihe ist.
109
+ def other_player_type
110
+ other_player.type
109
111
  end
110
112
 
111
- # @return [Integer] Aktuelle Runde (von 0 beginnend).
112
- def round
113
- turn / 2
113
+ # @return [Color] Farbe, der gerade an der Reihe ist.
114
+ def current_color
115
+ ordered_colors[current_color_index]
114
116
  end
115
117
 
118
+ # @return [Array<PieceShape>] Array aller Shapes, der gegebenen Farbe, die noch nicht gelegt wurden
116
119
  def undeployed_pieces(color)
117
120
  case color
118
- when PlayerColor::RED
121
+ when Color::RED
119
122
  undeployed_red_pieces
120
- when PlayerColor::BLUE
123
+ when Color::BLUE
121
124
  undeployed_blue_pieces
125
+ when Color::YELLOW
126
+ undeployed_yellow_pieces
127
+ when Color::GREEN
128
+ undeployed_green_pieces
122
129
  end
123
130
  end
124
131
 
132
+ # @return [Array<PieceShape>] Array aller Shapes, der gegebenen Farbe, die schon gelegt wurden
125
133
  def deployed_pieces(color)
126
134
  board.deployed_pieces(color)
127
135
  end
128
136
 
137
+ # @return [Bool] Ob diese gamestate in der ersten Runde ist
138
+ def is_first_move?
139
+ round == 1
140
+ end
141
+
129
142
  # Führt einen Zug auf dem Spielzustand aus. Das Spielbrett wird entsprechend
130
143
  # modifiziert.
131
144
  #
@@ -140,6 +153,11 @@ class GameState
140
153
  !condition.nil?
141
154
  end
142
155
 
156
+ # Entfernt die jetzige Farbe aus der Farbrotation
157
+ def remove_active_color
158
+ ordered_colors.delete current_color
159
+ end
160
+
143
161
  # @return [Player] Der Spieler, der das Spiel gewonnen hat, falls dies schon
144
162
  # entschieden ist. Sonst false.
145
163
  def winner
@@ -156,22 +174,23 @@ class GameState
156
174
  # Rundenlimits beendet wird, hat der Spieler mit den meisten Punkten gewonnen.
157
175
  #
158
176
  # @param player [Player] Der Spieler, dessen Punkte berechnet werden sollen.
159
- # @return [Integer] Die Punkte des Spielers, entspricht der Anzahl der Fische
160
- # im größten Schwarm des Spielers.
161
- def points_for_player(player)
177
+ # @return [Integer] Die Punkte des Spielers
178
+ def points_for_player(_player)
162
179
  # TODO
163
180
  -1
164
181
  end
165
182
 
166
183
  def ==(other)
167
184
  turn == other.turn &&
168
- start_player_color == other.start_player_color &&
169
- current_player_color == other.current_player_color &&
170
- red == other.red &&
171
- blue == other.blue &&
172
- board == other.board &&
173
- lastMove == other.lastMove &&
174
- condition == other.condition
185
+ start_color == other.start_color &&
186
+ current_color == other.current_color &&
187
+ blue == other.blue &&
188
+ yellow == other.yellow &&
189
+ red == other.red &&
190
+ green == other.green &&
191
+ board == other.board &&
192
+ lastMove == other.lastMove &&
193
+ condition == other.condition
175
194
  end
176
195
 
177
196
  # Erzeugt eine Kopie des Spielzustandes. Änderungen an dieser Kopie
@@ -186,9 +205,8 @@ class GameState
186
205
  @current_player_color = other_player_color
187
206
  end
188
207
 
189
- # @return [Array<Field>] Alle Felder mit Fischen des Spielers, der gerade an der Reihe ist.
208
+ # @return [Array<Field>] Alle Felder mit Blöcken des Spielers, der gerade an der Reihe ist.
190
209
  def own_fields
191
- board.fields_of_color(current_player_color)
210
+ board.fields_of_color(current_color)
192
211
  end
193
-
194
212
  end
@@ -1,5 +1,5 @@
1
+ # frozen_string_literal: true
1
2
  module HasHints
2
-
3
3
  # @!attribute [r] hints
4
4
  # @return [Array<DebugHint>] Hinweise, die an den Zug angeheftet werden sollen. Siehe {DebugHint}.
5
5
  attr_reader :hints
@@ -1,4 +1,5 @@
1
1
  # encoding: utf-8
2
+ # frozen_string_literal: true
2
3
 
3
4
  # Exception, die geworfen wird, wenn ein ungültiger Zug ausgeführt wird.
4
5
  # @see GameRuleLogic#perform_move
@@ -1,4 +1,5 @@
1
1
  # coding: utf-8
2
+ # frozen_string_literal: true
2
3
  require 'logger'
3
4
 
4
5
  # Dieses Modul kann inkludiert werden, um eine Logausgabe auf der Konsole verwenden zu können.
@@ -1,4 +1,5 @@
1
1
  # encoding: UTF-8
2
+ # frozen_string_literal: false
2
3
 
3
4
  require 'socket'
4
5
  require 'rexml/document'
@@ -122,5 +123,4 @@ class Network
122
123
  def emptyReceiveBuffer
123
124
  @receive_buffer = ''
124
125
  end
125
-
126
126
  end
@@ -1,31 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Ein Spielstein mit Ausrichtung, Koordinaten und Farbe
1
4
  class Piece
5
+ # @!attribute [r] Farbe
6
+ # @return [Color]
7
+ attr_reader :color
2
8
 
3
- # @!attribute [r] type
4
- # @return [PieceType]
5
- attr_reader :type
9
+ # @!attribute [r] Form
10
+ # @return [PieceShape]
11
+ attr_reader :kind
6
12
 
7
- # @!attribute [r] color
8
- # @return [PlayerColor]
9
- attr_reader :color
13
+ # @!attribute [r] Drehung
14
+ # @return [Rotation]
15
+ attr_reader :rotation
16
+
17
+ # @!attribute [r] Ob der Stein an der Y-Achse gespiegelt ist
18
+ # @return [Boolean]
19
+ attr_reader :is_flipped
20
+
21
+ # @!attribute [r] Koordinaten
22
+ # @return [Coordinates]
23
+ attr_reader :position
24
+
25
+ attr_reader :coords
10
26
 
11
- def initialize(color, type)
12
- @type = type
27
+ # Erstellt einen neuen leeren Spielstein.
28
+ def initialize(color, kind, rotation = Rotation::NONE, is_flipped = false, position = Coordinates.origin)
13
29
  @color = color
14
- end
30
+ @kind = kind
31
+ @rotation = rotation
32
+ @is_flipped = is_flipped
33
+ @position = position
15
34
 
16
- def ==(other)
17
- type == other.type && color == other.color
35
+ @coords = coords_priv
18
36
  end
19
37
 
20
- def owner
21
- color
38
+ def ==(other)
39
+ color == other.color &&
40
+ kind == other.kind &&
41
+ rotation == other.rotation &&
42
+ is_flipped == other.is_flipped &&
43
+ position == other.position
22
44
  end
23
45
 
24
46
  def to_s
25
- color.value + type.value
47
+ "#{color.key} #{kind.key} at #{position} rotation #{rotation.key}#{is_flipped ? ' (flipped)' : ''}"
26
48
  end
27
49
 
28
50
  def inspect
29
51
  to_s
30
52
  end
53
+
54
+ private
55
+ def coords_priv
56
+ kind.transform(@rotation, @is_flipped).transform do |it|
57
+ Coordinates.new(it.x + @position.x, it.y + @position.y)
58
+ end.coordinates
59
+ end
31
60
  end
@@ -0,0 +1,109 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'typesafe_enum'
4
+
5
+ require_relative 'coordinates'
6
+ require_relative 'coordinate_set'
7
+
8
+ # Die Form eines Spielsteins. Es gibt folgende Formen:
9
+ #
10
+ # MONO
11
+ # DOMINO
12
+ # TRIO_L
13
+ # TRIO_I
14
+ # TETRO_O
15
+ # TETRO_T
16
+ # TETRO_I
17
+ # TETRO_L
18
+ # TETRO_Z
19
+ # PENTO_L
20
+ # PENTO_T
21
+ # PENTO_V
22
+ # PENTO_S
23
+ # PENTO_Z
24
+ # PENTO_I
25
+ # PENTO_P
26
+ # PENTO_W
27
+ # PENTO_U
28
+ # PENTO_R
29
+ # PENTO_X
30
+ # PENTO_Y
31
+ #
32
+ # Zugriff z.B. mit PieceShape::PENTO_S
33
+ class PieceShape < TypesafeEnum::Base
34
+ def self.c(x, y)
35
+ Coordinates.new(x, y)
36
+ end
37
+ new :MONO, [c(0, 0)]
38
+ new :DOMINO, [c(0, 0), c(1, 0)]
39
+ new :TRIO_L, [c(0, 0), c(0, 1), c(1, 1)]
40
+ new :TRIO_I, [c(0, 0), c(0, 1), c(0, 2)]
41
+ new :TETRO_O, [c(0, 0), c(1, 0), c(0, 1), c(1, 1)]
42
+ new :TETRO_T, [c(0, 0), c(1, 0), c(2, 0), c(1, 1)]
43
+ new :TETRO_I, [c(0, 0), c(0, 1), c(0, 2), c(0, 3)]
44
+ new :TETRO_L, [c(0, 0), c(0, 1), c(0, 2), c(1, 2)]
45
+ new :TETRO_Z, [c(0, 0), c(1, 0), c(1, 1), c(2, 1)]
46
+ new :PENTO_L, [c(0, 0), c(0, 1), c(0, 2), c(0, 3), c(1, 3)]
47
+ new :PENTO_T, [c(0, 0), c(1, 0), c(2, 0), c(1, 1), c(1, 2)]
48
+ new :PENTO_V, [c(0, 0), c(0, 1), c(0, 2), c(1, 2), c(2, 2)]
49
+ new :PENTO_S, [c(1, 0), c(2, 0), c(3, 0), c(0, 1), c(1, 1)]
50
+ new :PENTO_Z, [c(0, 0), c(1, 0), c(1, 1), c(1, 2), c(2, 2)]
51
+ new :PENTO_I, [c(0, 0), c(0, 1), c(0, 2), c(0, 3), c(0, 4)]
52
+ new :PENTO_P, [c(0, 0), c(1, 0), c(0, 1), c(1, 1), c(0, 2)]
53
+ new :PENTO_W, [c(0, 0), c(0, 1), c(1, 1), c(1, 2), c(2, 2)]
54
+ new :PENTO_U, [c(0, 0), c(0, 1), c(1, 1), c(2, 1), c(2, 0)]
55
+ new :PENTO_R, [c(0, 1), c(1, 1), c(1, 2), c(2, 1), c(2, 0)]
56
+ new :PENTO_X, [c(1, 0), c(0, 1), c(1, 1), c(2, 1), c(1, 2)]
57
+ new :PENTO_Y, [c(0, 1), c(1, 0), c(1, 1), c(1, 2), c(1, 3)]
58
+
59
+ @transformations
60
+ Transform = Struct.new(:r, :f, :coords)
61
+
62
+ # Anzahl Felder, die der Stein belegt
63
+ def size
64
+ value.size
65
+ end
66
+
67
+ # Die Felder, die der Stein belegt
68
+ def coordinates
69
+ CoordinateSet.new(value)
70
+ end
71
+
72
+ # Eine Koordinate, die das kleinstmögliche Rechteck beschreibt, welches alle Felder umfasst.
73
+ def dimension
74
+ coordinates.area
75
+ end
76
+
77
+ # Erzeugt eine nach Rotation und Flip transformierte Form
78
+ def transform(rotation, flip)
79
+ coordinates.rotate(rotation).flip(flip)
80
+ end
81
+
82
+ # Gibt alle Kombinationen aus Rotation und Flipping zurück, welche zu einzigartigen
83
+ # Koordinatenmengen dieser Form führen.
84
+ # @return [Array<Transform>] Transform Structs mit Rotation r, Boolean f
85
+ def unique_transforms()
86
+ if not defined? @transformations then
87
+ existing_transforms = []
88
+
89
+ Rotation.each do |r|
90
+ [true, false].each do |f|
91
+ new_transform = Transform.new(r, f, transform(r, f))
92
+
93
+ if existing_transforms.none? { |t| t.coords == new_transform.coords } then
94
+ existing_transforms << new_transform
95
+ end
96
+ end
97
+ end
98
+
99
+ @transformations = existing_transforms
100
+ end
101
+
102
+ @transformations
103
+ end
104
+
105
+ # Gibt den Form Namen zurück
106
+ def to_s
107
+ self.key.to_s
108
+ end
109
+ end