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,3 @@
1
- # encoding: utf-8
2
1
  # frozen_string_literal: true
3
2
 
4
3
  require_relative './util/constants'
@@ -15,36 +14,62 @@ 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
26
17
 
27
- # @!attribute [r] undeployed_red_pieces
28
- # @return [Player] Die nicht gesetzten Spielsteine des roten Spielers
29
- attr_accessor :undeployed_red_pieces
18
+ # @!attribute [rw] round
19
+ # @return [Integer] Aktuelle Rundennummer (von 1 beginnend)
20
+ attr_accessor :round
21
+
22
+ # @!attribute [rw] startColor
23
+ # @return [Color] Die Farbe, die zuerst legen darf
24
+ attr_accessor :start_color
25
+
26
+ # @!attribute [rw] valid_colors
27
+ # @return [Array<Color>] Ein Array aller Farben die ziehen können in
28
+ # der Reihenfolge in der sie drankommen
29
+ attr_accessor :valid_colors
30
+
31
+ # @!attribute [rw] ordered_colors
32
+ # @return [Array<Color>] Ein Array aller Farben in
33
+ # der Reihenfolge in der sie drankommen
34
+ attr_accessor :ordered_colors
30
35
 
31
36
  # @!attribute [r] undeployed_blue_pieces
32
- # @return [Player] Die nicht gesetzten Spielsteine des roten Spielers
37
+ # @return [Array<PieceShape>] Die blauen, nicht gesetzten Spielsteine
33
38
  attr_accessor :undeployed_blue_pieces
34
39
 
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
40
+ # @!attribute [r] undeployed_yellow_pieces
41
+ # @return [Array<PieceShape>] Die gelben, nicht gesetzten Spielsteine
42
+ attr_accessor :undeployed_yellow_pieces
43
+
44
+ # @!attribute [r] undeployed_red_pieces
45
+ # @return [Array<PieceShape>] Die roten, nicht gesetzten Spielsteine
46
+ attr_accessor :undeployed_red_pieces
47
+
48
+ # @!attribute [r] undeployed_green_pieces
49
+ # @return [Array<PieceShape>] Die grünen, nicht gesetzten Spielsteine
50
+ attr_accessor :undeployed_green_pieces
51
+
52
+ # @!attribute [r] player_one
53
+ # @return [Player] Der erste Spieler
54
+ attr_reader :player_one
55
+
56
+ # @!attribute [r] player_two
57
+ # @return [Player] Der zweite Spieler
58
+ attr_reader :player_two
59
+
41
60
  # @!attribute [rw] board
42
61
  # @return [Board] Das aktuelle Spielbrett
43
62
  attr_accessor :board
63
+
64
+ # @!attribute [rw] startPiece
65
+ # @return [PieceShape] Der Stein, der im ersten Zug von allen Farben gelegt werden muss
66
+ attr_accessor :start_piece
67
+
44
68
  # @!attribute [rw] last_move
45
69
  # @return [Move] Der zuletzt gemachte Zug (ist nil vor dem ersten Zug, also
46
70
  # bei turn == 0)
47
71
  attr_accessor :last_move
72
+
48
73
  # @!attribute [rw] condition
49
74
  # @return [Condition] Gewinner und Gewinngrund, falls das Spiel bereits
50
75
  # entschieden ist, sonst nil.
@@ -54,78 +79,86 @@ class GameState
54
79
  def field(x, y)
55
80
  board.field(x, y)
56
81
  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
82
 
83
+ # Erstellt einen neuen leeren Spielstand.
74
84
  def initialize
75
- @current_player_color = PlayerColor::RED
76
- @start_player_color = PlayerColor::RED
85
+ @ordered_colors = [Color::BLUE, Color::YELLOW, Color::RED, Color::GREEN]
77
86
  @board = Board.new
78
87
  @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)
88
+ @undeployed_blue_pieces = PieceShape.to_a
89
+ @undeployed_yellow_pieces = PieceShape.to_a
90
+ @undeployed_red_pieces = PieceShape.to_a
91
+ @undeployed_green_pieces = PieceShape.to_a
92
+ @start_piece = GameRuleLogic.get_random_pentomino
93
+ @start_color = Color::BLUE
81
94
  end
82
95
 
83
96
  # Fügt einen Spieler zum Spielzustand hinzu.
84
97
  #
85
98
  # @param player [Player] Der hinzuzufügende Spieler.
86
99
  def add_player(player)
87
- if player.color == PlayerColor::RED
88
- @red = player
89
- elsif player.color == PlayerColor::BLUE
90
- @blue = player
100
+ case player.type
101
+ when PlayerType::ONE
102
+ @player_one = player
103
+ when PlayerType::TWO
104
+ @player_two = player
91
105
  end
92
106
  end
93
107
 
94
108
  # @return [Player] Spieler, der gerade an der Reihe ist.
95
109
  def current_player
96
- return red if current_player_color == PlayerColor::RED
97
- return blue if current_player_color == PlayerColor::BLUE
110
+ turn.even? ? player_one : player_two
98
111
  end
99
112
 
100
113
  # @return [Player] Spieler, der gerade nicht an der Reihe ist.
101
114
  def other_player
102
- return blue if current_player_color == PlayerColor::RED
103
- return red if current_player_color == PlayerColor::BLUE
115
+ turn.even? ? player_two : player_one
104
116
  end
105
117
 
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)
118
+ # @return [PlayerType] Typ des Spielers, der gerade nicht an der Reihe ist.
119
+ def other_player_type
120
+ other_player.type
109
121
  end
110
122
 
111
- # @return [Integer] Aktuelle Runde (von 0 beginnend).
112
- def round
113
- turn / 2
123
+ # @return [Color] Der jetzige Index in der Zug Reihenfolge der Farben.
124
+ def current_color_index
125
+ turn % 4
114
126
  end
115
127
 
128
+ # @return [Color] Farbe, der gerade an der Reihe ist.
129
+ def current_color
130
+ ordered_colors[current_color_index]
131
+ end
132
+
133
+ # @return [Color] Farbe des aktuellen Spielers, die gerade nicht an der Reihe ist.
134
+ def other_color
135
+ Color.find_by_ord((current_color.ord + 2) % 4)
136
+ end
137
+
138
+ # @return [Array<PieceShape>] Array aller Shapes, der gegebenen Farbe, die noch nicht gelegt wurden
116
139
  def undeployed_pieces(color)
117
140
  case color
118
- when PlayerColor::RED
141
+ when Color::RED
119
142
  undeployed_red_pieces
120
- when PlayerColor::BLUE
143
+ when Color::BLUE
121
144
  undeployed_blue_pieces
145
+ when Color::YELLOW
146
+ undeployed_yellow_pieces
147
+ when Color::GREEN
148
+ undeployed_green_pieces
122
149
  end
123
150
  end
124
151
 
152
+ # @return [Array<PieceShape>] Array aller Shapes, der gegebenen Farbe, die schon gelegt wurden
125
153
  def deployed_pieces(color)
126
154
  board.deployed_pieces(color)
127
155
  end
128
156
 
157
+ # @return [Bool] Ob diese gamestate in der ersten Runde ist
158
+ def is_first_move?
159
+ round == 1
160
+ end
161
+
129
162
  # Führt einen Zug auf dem Spielzustand aus. Das Spielbrett wird entsprechend
130
163
  # modifiziert.
131
164
  #
@@ -140,6 +173,11 @@ class GameState
140
173
  !condition.nil?
141
174
  end
142
175
 
176
+ # Entfernt die jetzige Farbe aus der Farbrotation
177
+ def remove_active_color
178
+ ordered_colors.delete current_color
179
+ end
180
+
143
181
  # @return [Player] Der Spieler, der das Spiel gewonnen hat, falls dies schon
144
182
  # entschieden ist. Sonst false.
145
183
  def winner
@@ -156,22 +194,23 @@ class GameState
156
194
  # Rundenlimits beendet wird, hat der Spieler mit den meisten Punkten gewonnen.
157
195
  #
158
196
  # @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)
197
+ # @return [Integer] Die Punkte des Spielers
198
+ def points_for_player(_player)
162
199
  # TODO
163
200
  -1
164
201
  end
165
202
 
166
203
  def ==(other)
167
204
  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
205
+ start_color == other.start_color &&
206
+ current_color == other.current_color &&
207
+ blue == other.blue &&
208
+ yellow == other.yellow &&
209
+ red == other.red &&
210
+ green == other.green &&
211
+ board == other.board &&
212
+ lastMove == other.lastMove &&
213
+ condition == other.condition
175
214
  end
176
215
 
177
216
  # Erzeugt eine Kopie des Spielzustandes. Änderungen an dieser Kopie
@@ -186,9 +225,8 @@ class GameState
186
225
  @current_player_color = other_player_color
187
226
  end
188
227
 
189
- # @return [Array<Field>] Alle Felder mit Fischen des Spielers, der gerade an der Reihe ist.
228
+ # @return [Array<Field>] Alle Felder mit Blöcken des Spielers, der gerade an der Reihe ist.
190
229
  def own_fields
191
- board.fields_of_color(current_player_color)
230
+ board.fields_of_color(current_color)
192
231
  end
193
-
194
232
  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,89 @@
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
10
24
 
11
- def initialize(color, type)
12
- @type = type
25
+ # @!attribute [r] Ein Array der Positionsdaten aller Bestandteile von dem Stein in Board Koordinaten, also schon ggf. gedreht und um position versetzt.
26
+ # return [Array<Coordinates>]
27
+ attr_reader :coords
28
+
29
+ # Erstellt einen neuen leeren Spielstein.
30
+ def initialize(color, kind, rotation = Rotation::NONE, is_flipped = false, position = Coordinates.origin)
13
31
  @color = color
32
+ @kind = kind
33
+ @rotation = rotation
34
+ @is_flipped = is_flipped
35
+ @position = position
36
+
37
+ @coords = coords_priv
14
38
  end
15
39
 
16
- def ==(other)
17
- type == other.type && color == other.color
40
+ # Dreht den Stein
41
+ def rotate!(rotation)
42
+ @rotation = @rotation.rotate(rotation)
43
+ @coords = coords_priv
44
+ end
45
+
46
+ # Flipped den Stein
47
+ def flip!(f = true)
48
+ @is_flipped = @is_flipped ^ f
49
+ @coords = coords_priv
18
50
  end
19
51
 
20
- def owner
21
- color
52
+ # Setzt den Stein auf eine Position
53
+ def locate!(position)
54
+ @position = position
55
+ @coords = coords_priv
56
+ end
57
+
58
+ # Verschiebt den Stein
59
+ def move!(shift)
60
+ @position = position + shift
61
+ @coords = coords_priv
62
+ end
63
+
64
+ # Gibt die Fläche der transformierten Steinform von diesem Stein zurück
65
+ def area()
66
+ CoordinateSet.new(coords).area
67
+ end
68
+
69
+ def ==(other)
70
+ color == other.color &&
71
+ coords == other.coords
22
72
  end
23
73
 
24
74
  def to_s
25
- color.value + type.value
75
+ "#{color.key} #{kind.key} at #{position} rotation #{rotation.key}#{is_flipped ? ' (flipped)' : ''}"
26
76
  end
27
77
 
28
78
  def inspect
29
79
  to_s
30
80
  end
81
+
82
+ private
83
+
84
+ def coords_priv
85
+ kind.transform(@rotation, @is_flipped).transform do |it|
86
+ Coordinates.new(it.x + @position.x, it.y + @position.y)
87
+ end.coordinates
88
+ end
31
89
  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