software_challenge_client 20.2.4 → 21.2.0

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 -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