software_challenge_client 1.2.1 → 19.0.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 (34) hide show
  1. checksums.yaml +4 -4
  2. data/Dockerfile +3 -0
  3. data/README.md +64 -26
  4. data/example/client.rb +1 -71
  5. data/example/main.rb +1 -1
  6. data/lib/software_challenge_client.rb +4 -2
  7. data/lib/software_challenge_client/board.rb +66 -19
  8. data/lib/software_challenge_client/client_interface.rb +10 -5
  9. data/lib/software_challenge_client/condition.rb +2 -2
  10. data/lib/software_challenge_client/coordinates.rb +17 -0
  11. data/lib/software_challenge_client/debug_hint.rb +8 -4
  12. data/lib/software_challenge_client/direction.rb +53 -0
  13. data/lib/software_challenge_client/field.rb +26 -12
  14. data/lib/software_challenge_client/field_type.rb +25 -19
  15. data/lib/software_challenge_client/game_rule_logic.rb +230 -0
  16. data/lib/software_challenge_client/game_state.rb +45 -191
  17. data/lib/software_challenge_client/invalid_move_exception.rb +6 -8
  18. data/lib/software_challenge_client/line.rb +126 -0
  19. data/lib/software_challenge_client/line_direction.rb +15 -0
  20. data/lib/software_challenge_client/logging.rb +3 -2
  21. data/lib/software_challenge_client/move.rb +51 -38
  22. data/lib/software_challenge_client/network.rb +3 -1
  23. data/lib/software_challenge_client/player.rb +0 -39
  24. data/lib/software_challenge_client/player_color.rb +23 -13
  25. data/lib/software_challenge_client/protocol.rb +20 -83
  26. data/lib/software_challenge_client/runner.rb +2 -1
  27. data/lib/software_challenge_client/util/constants.rb +8 -5
  28. data/lib/software_challenge_client/version.rb +1 -1
  29. data/software_challenge_client.gemspec +2 -0
  30. metadata +24 -8
  31. data/lib/software_challenge_client/action.rb +0 -217
  32. data/lib/software_challenge_client/card_type.rb +0 -13
  33. data/lib/software_challenge_client/field_unavailable_exception.rb +0 -17
  34. data/lib/software_challenge_client/game_rules.rb +0 -376
@@ -0,0 +1,53 @@
1
+ # encoding: utf-8
2
+
3
+ require 'typesafe_enum'
4
+
5
+ # Die acht möglichen Bewegungsrichtungen auf dem Spielbrett. Die Richtungen sind:
6
+ #
7
+ # - UP
8
+ # - UP_RIGHT
9
+ # - RIGHT
10
+ # - DOWN_RIGHT
11
+ # - DOWN
12
+ # - DOWN_LEFT
13
+ # - LEFT
14
+ # - UP_LEFT
15
+ #
16
+ # Zugriff erfolgt z.B. durch Direction::UP_RIGHT.
17
+ class Direction < TypesafeEnum::Base
18
+ new :UP
19
+ new :UP_RIGHT
20
+ new :RIGHT
21
+ new :DOWN_RIGHT
22
+ new :DOWN
23
+ new :DOWN_LEFT
24
+ new :LEFT
25
+ new :UP_LEFT
26
+
27
+ # Verschiebt den durch das Koordinatenpaar angegebenen Punkt in die
28
+ # entsprechende Richtung. Der resultierende Punkt kann ausserhalb des
29
+ # Spielbrettes liegen. Dies kann mit {GameRuleLogic#inside_bounds?} geprüft
30
+ # werden.
31
+ # @param coordinates [Coordinates] Das zu verschiebende Koordinatenpaar.
32
+ # @param distance [Integer] Um wieviele Felder in die Richtung verschoben werden soll.
33
+ def translate(coordinates, distance = 1)
34
+ case key
35
+ when :UP
36
+ Coordinates.new(coordinates.x, coordinates.y + distance)
37
+ when :UP_RIGHT
38
+ Coordinates.new(coordinates.x + distance, coordinates.y + distance)
39
+ when :RIGHT
40
+ Coordinates.new(coordinates.x + distance, coordinates.y)
41
+ when :DOWN_RIGHT
42
+ Coordinates.new(coordinates.x + distance, coordinates.y - distance)
43
+ when :DOWN
44
+ Coordinates.new(coordinates.x, coordinates.y - distance)
45
+ when :DOWN_LEFT
46
+ Coordinates.new(coordinates.x - distance, coordinates.y - distance)
47
+ when :LEFT
48
+ Coordinates.new(coordinates.x - distance, coordinates.y)
49
+ when :UP_LEFT
50
+ Coordinates.new(coordinates.x - distance, coordinates.y + distance)
51
+ end
52
+ end
53
+ end
@@ -1,31 +1,45 @@
1
1
  # encoding: UTF-8
2
2
  require_relative 'field_type'
3
3
 
4
- # Ein Feld des Spielfelds. Ein Spielfeld ist durch den index eindeutig identifiziert.
4
+ # Ein Feld des Spielfelds. Ein Spielfeld ist durch die Koordinaten eindeutig identifiziert.
5
5
  # Das type Attribut gibt an, um welchen Feldtyp es sich handelt
6
6
  class Field
7
7
  # @!attribute [rw] type
8
8
  # @return [FieldType] der Typ des Feldes
9
9
  attr_accessor :type
10
- # @!attribute [r] index
11
- # @return [Integer] der Index des Feldes (0 bis 64)
12
- attr_reader :index
10
+ # @!attribute [r] x
11
+ # @return [Integer] die X-Koordinate des Feldes (0 bis 9, 0 ist ganz links, 9 ist ganz rechts)
12
+ attr_reader :x
13
+ # @!attribute [r] y
14
+ # @return [Integer] die Y-Koordinate des Feldes (0 bis 9, 0 ist ganz unten, 9 ist ganz oben)
15
+ attr_reader :y
13
16
 
14
17
  # Konstruktor
15
18
  #
16
19
  # @param type [FieldType] Feldtyp
17
- # @param index [Integer] Index
18
- def initialize(type, index)
19
- self.type = type
20
- @index = index
20
+ # @param x [Integer] X-Koordinate
21
+ # @param y [Integer] Y-Koordinate
22
+ def initialize(x, y, type)
23
+ @type = type
24
+ @x = x
25
+ @y = y
21
26
  end
22
27
 
23
- def ==(another_field)
24
- return self.type == another_field.type &&
25
- self.index == another_field.index
28
+ # Vergleicht zwei Felder. Felder sind gleich, wenn sie gleiche Koordinaten und gleichen Typ haben.
29
+ # @return [Boolean] true bei Gleichheit, false sonst.
30
+ def ==(other)
31
+ type == other.type &&
32
+ x == other.x &&
33
+ y == other.y
26
34
  end
27
35
 
36
+ # @return [Coordinates] Die Koordinaten des Feldes als Koordinatenpaar.
37
+ def coordinates
38
+ Coordinates.new(x, y)
39
+ end
40
+
41
+ # @return [String] Textuelle Darstellung des Feldes.
28
42
  def to_s
29
- return "Feld ##{self.index}, Typ = #{self.type}"
43
+ "Feld (#{x},#{y}), Typ = #{type}"
30
44
  end
31
45
  end
@@ -1,24 +1,30 @@
1
1
  # encoding: utf-8
2
2
 
3
3
  require 'typesafe_enum'
4
- # Zahl- und Flaggenfelder Die veränderten Spielregeln sehen nur noch die
5
- # Felder 1,2 vor. Die Positionsfelder 3 und 4 wurden in Möhrenfelder
6
- # umgewandelt, und (1,5,6) sind jetzt Position-1-Felder.
4
+ # Der Typ eines Feldes des Spielbrettes. Es gibt folgende Typen:
5
+ # - EMPTY
6
+ # - RED
7
+ # - BLUE
8
+ # - OBSTRUCTED
9
+ #
10
+ # Zugriff z.B. mit FieldType::RED
7
11
  class FieldType < TypesafeEnum::Base
8
- new :POSITION_1, '1'
9
- new :POSITION_2, '2'
10
- # Igelfeld
11
- new :HEDGEHOG, 'I'
12
- # Salatfeld
13
- new :SALAD, 'S'
14
- # Karottenfeld
15
- new :CARROT, 'C'
16
- # Hasenfeld
17
- new :HARE, 'H'
18
- # außerhalb des Spielfeldes
19
- new :INVALID, 'X'
20
- # Zielfeld
21
- new :GOAL, 'G'
22
- # Startfeld
23
- new :START, '0'
12
+ new :EMPTY, '~'
13
+ new :RED, 'R'
14
+ new :BLUE, 'B'
15
+ new :OBSTRUCTED, 'O'
16
+
17
+ # @param field_type [FieldType] Der Feldtyp, zu dem die Spielerfarbe ermittelt werden soll.
18
+ # @return [PlayerColor] Die zum Feldtyp gehörende Spielerfarbe, also PlayerColor::RED für FieldType::RED und PlayerColor::BLUE für FieldType::BLUE. In allen anderen Fällen PlayerColor::NONE.
19
+ # @see PlayerColor#field_type
20
+ def self.player_color(field_type)
21
+ case field_type
22
+ when FieldType::RED
23
+ PlayerColor::RED
24
+ when FieldType::BLUE
25
+ PlayerColor::BLUE
26
+ else
27
+ PlayerColor::NONE
28
+ end
29
+ end
24
30
  end
@@ -0,0 +1,230 @@
1
+ # coding: utf-8
2
+ # frozen_string_literal: true
3
+
4
+ require_relative 'field_type'
5
+ require_relative 'line'
6
+ require_relative './util/constants'
7
+
8
+ # Methoden, welche die Spielregeln von Piranhas abbilden.
9
+ #
10
+ # Es gibt hier viele Helfermethoden, die von den beiden Hauptmethoden {GameRuleLogic#valid_move?} und {GameRuleLogic.possible_moves} benutzt werden.
11
+ class GameRuleLogic
12
+
13
+ include Constants
14
+
15
+ # Fügt einem leeren Spielfeld zwei Krakenfelder hinzu. Die beiden Felder
16
+ # liegen nicht auf derselben Horizontalen, Vertikalen oder Diagonalen und sind
17
+ # mindestens zwei Felder von den Rändern des Spielbrettes entfernt.
18
+ #
19
+ # Diese Methode ist dazu gedacht, ein initiales Spielbrett regelkonform zu generieren.
20
+ #
21
+ # @param board [Board] Das zu modifizierende Spielbrett. Es wird nicht
22
+ # geprüft, ob sich auf dem Spielbrett bereits Krakenfelder befinden.
23
+ # @return [Board] Das modifizierte Spielbrett.
24
+ def self.add_blocked_fields(board)
25
+ number_of_blocked_fields = 2
26
+ lower_bound = 2 # first row or column, in which blocked fields are allowed
27
+ upper_bound = 7 # last row or column, in which blocked fields are allowed
28
+
29
+ # create a list of coordinates for fields which may be blocked
30
+ blockable_field_coordinates = (lower_bound..upper_bound).to_a.map do |x|
31
+ (lower_bound..upper_bound).to_a.map do |y|
32
+ Coordinate.new(x, y)
33
+ end
34
+ end.flatten
35
+
36
+ # set fields with randomly selected coordinates to blocked coordinates may
37
+ # not lay on same horizontal, vertical or diagonal lines with other selected
38
+ # coordinates
39
+ number_of_blocked_fields.times do
40
+ selected_coords = blockable_field_coordinates.sample
41
+ board.change_field(selectedCoords, FieldType::OBSTRUCTED)
42
+ # remove field coordinates and fields on horizontal, vertical and diagonal
43
+ # lines:
44
+ coordinates_to_remove = ALL_DIRECTIONS.map do |direction|
45
+ Line.new(selected_coords, direction).to_a
46
+ end.flatten
47
+ blockable_field_coordinates = blockable_field_coordinates.filter do |c|
48
+ coordinates_to_remove.none? do |to_remove|
49
+ c.x == to_remove.x && c.y == to_remove.y
50
+ end
51
+ end
52
+ end
53
+ board
54
+ end
55
+
56
+ # Ermittlung der Anzahl der Fische auf einer Line des Spielbrettes.
57
+ #
58
+ # @param board [Board] Das zu betrachtende Spielbrett.
59
+ # @param start [Coordinates] Ein Feld auf der Linie.
60
+ # @param direction [LineDirection] Die Ausrichtung der Linie (vertikal, horizontal oder diagonal).
61
+ # @return [Integer] Anzahl der Fische auf der Linie.
62
+ def self.count_fish(board, start, direction)
63
+ # filter function for fish field type
64
+ fish = proc { |f| f.type == FieldType::RED || f.type == FieldType::BLUE }
65
+ Line.new(start, direction).to_a.map do |p|
66
+ board.field(p.x, p.y)
67
+ end.select(&fish).size
68
+ end
69
+
70
+ # @return [Coordinates] Die Zielkoordinaten eines Spielzuges auf einem Spielbrett.
71
+ def self.target_coordinates(move, board)
72
+ speed = GameRuleLogic.count_fish(
73
+ board, move.from_field,
74
+ Line.line_direction_for_direction(move.direction)
75
+ )
76
+ move.target_field(speed)
77
+ end
78
+
79
+ # @return [Field] Das Zielfeld eines Spielzuges auf einem Spielbrett.
80
+ def self.move_target(move, board)
81
+ c = GameRuleLogic.target_coordinates(move, board)
82
+ board.field(c.x, c.y)
83
+ end
84
+
85
+ # Prüft, ob sich die gegebenen Koordinaten innerhalb des Spielbrettes befinden.
86
+ # @return [Boolean]
87
+ def self.inside_bounds?(coordinates)
88
+ coordinates.x >= 0 &&
89
+ coordinates.x < SIZE &&
90
+ coordinates.y >= 0 &&
91
+ coordinates.y < SIZE
92
+ end
93
+
94
+ # Ermittelt, ob der gegebene Feldtyp für den Spieler mit der angegebenen Farbe ein nicht überspringbares Hindernis darstellt.
95
+ # @param field_type [FieldType]
96
+ # @param moving_player_color [PlayerColor]
97
+ # @return [Boolean] true, falls es ein Hindernis ist, false sonst.
98
+ def self.obstacle?(field_type, moving_player_color)
99
+ field_type == PlayerColor.field_type(
100
+ PlayerColor.opponent_color(moving_player_color)
101
+ )
102
+ end
103
+
104
+ # Ermittelt, ob sich zwischen den angegebenen Feldern kein Hindernis befindet.
105
+ # @param from_field [Coordinates] Startfeld
106
+ # @param to_field [Coordinates] Zielfeld
107
+ # @param direction [LineDirection] Ausrichtung der Linie zwischen Start- und Zielfeld.
108
+ # @param color [PlayerColor] Farbe des ziehenden Spielers.
109
+ # @param board [Board] Das aktuelle Spielbrett.
110
+ # @return [Boolean] true, falls der Spieler mit der angegebenen Farbe zwischen den beiden Punkten ein Hindernis vorfindet, false sonst.
111
+ def self.obstacle_between?(from_field, direction, to_field, color, board)
112
+ Line.new(from_field, direction)
113
+ .to_a
114
+ .select { |c| Line.between(from_field, to_field, direction).call(c) }
115
+ .any? { |f| GameRuleLogic.obstacle?(board.field(f.x, f.y).type, color) }
116
+ end
117
+
118
+ # Ermittelt, ob der Spieler mit der angegebenen Farbe einen Fisch auf dem Feld mit den angegebenen Koordinaten besitzt.
119
+ # @param target [Coordinates] Koordinaten des Feldes.
120
+ # @param moving_player_color [PlayerColor] Farbe des Spielers, der einen Zug machen will.
121
+ # @param board [Board] Aktuelles Spielbrett.
122
+ # @return [Boolean] true falls sich auf dem Feld ein Fisch mit der richtigen Farbe befindet (Rot für roten Spieler, Blau für blauen Spieler), false sonst.
123
+ def self.valid_move_target(target, moving_player_color, board)
124
+ target_field_type = board.field(target.x, target.y).type
125
+ target_field_type == FieldType::EMPTY ||
126
+ target_field_type == PlayerColor.field_type(
127
+ PlayerColor.opponent_color(moving_player_color)
128
+ )
129
+ end
130
+
131
+ # Ermittelt, ob der gegebene Zug regelkonform ausgeführt werden kann.
132
+ # @param move [Move] Der zu prüfende Zug
133
+ # @param board [Board] Spielbrett, auf dem der Zug ausgeführt werden soll.
134
+ # @param current_player_color [PlayerColor] Farbe des Spielers, der den Zug ausführen soll.
135
+ # @return [Boolean] true falls der Zug gültig ist, false sonst.
136
+ def self.valid_move?(move, board, current_player_color)
137
+ from_field_type = board.field(move.x, move.y).type
138
+ return false unless
139
+ [FieldType::BLUE, FieldType::RED].include? from_field_type
140
+ return false unless
141
+ current_player_color == FieldType.player_color(from_field_type)
142
+
143
+ return false unless
144
+ GameRuleLogic.inside_bounds?(
145
+ GameRuleLogic.target_coordinates(move, board)
146
+ )
147
+
148
+ target = GameRuleLogic.move_target(move, board)
149
+
150
+ GameRuleLogic.valid_move_target(target, current_player_color, board) &&
151
+ !GameRuleLogic.obstacle_between?(
152
+ move.from_field,
153
+ Line.line_direction_for_direction(move.direction),
154
+ target, current_player_color, board
155
+ )
156
+ end
157
+
158
+ # Ermittelt alle möglichen Züge von einem bestimmten Feld aus.
159
+ # @param board [Board] Aktuelles Spielbrett
160
+ # @param field [Field] Das Feld, von dem die Züge ausgehen sollen.
161
+ # @param current_player_color [PlayerColor] Farbe des Spielers, der den Zug macht.
162
+ # @return [Array<Move>] Liste von möglichen Zügen.
163
+ def self.possible_moves(board, field, current_player_color)
164
+ Direction.map { |direction| Move.new(field.x, field.y, direction) }
165
+ .select do |m|
166
+ GameRuleLogic.valid_move?(m, board, current_player_color)
167
+ end
168
+ end
169
+
170
+ # Ermittelt die Schwarmgröße eines Spielers auf dem Spielbrett.
171
+ # @param board [Board] Das zu betrachtende Spielbrett.
172
+ # @param player_color [PlayerColor] Farbe des Spielers, für den die Schwarmgröße ermittelt werden soll.
173
+ # @return [Integer] Anzahl der Fische im größten Schwarm des Spielers.
174
+ def self.swarm_size(board, player_color)
175
+ GameRuleLogic.greatest_swarm_from_fields(
176
+ board,
177
+ board.fields_of_type(
178
+ PlayerColor.field_type(player_color)
179
+ ).to_set,
180
+ Set.new
181
+ ).size
182
+ end
183
+
184
+ # @return [Array<Field>] Alle direkten Nachbarfelder des gegebenen Feldes. Für Felder im Inneren des Spielbrettes gibt es acht Nachbarfelder. Für Randfelder vier oder drei Nachbarfelder.
185
+ def self.neighbours(board, field)
186
+ Direction
187
+ .map { |d| d.translate(field.coordinates) }
188
+ .select { |c| GameRuleLogic.inside_bounds?(c) }
189
+ .map { |c| board.field_at(c) }
190
+ end
191
+
192
+ # Hilfsfunktion für {GameRuleLogic.swarm_size}.
193
+ # Ermittelt die größte zusammenhängende Menge von Feldern aus einer gegebenen Menge von Feldern.
194
+ # @param board [Board] Das zu betrachtende Spielbrett.
195
+ # @param fields_to_check [Set<Field>] Menge der Felder, aus der die größte zusammenhängende Menge ermittelt werden soll.
196
+ # @param current_biggest_swarm [Set<Field>] Aktuell größte zusammenhängende Feldmenge. Für rekursiven Aufruf.
197
+ # @return [Set<Field>]
198
+ def self.greatest_swarm_from_fields(board, fields_to_check, current_biggest_swarm = Set.new)
199
+ # stop searching when the size of the current found biggest set is bigger than the rest of the fields
200
+ return current_biggest_swarm if current_biggest_swarm.size > fields_to_check.size
201
+
202
+ # start a new set of adjacent fields with the first field in fields_to_check
203
+ current_swarm = Set.new
204
+ field = fields_to_check.to_a.first
205
+ fields_to_check.delete(field)
206
+ current_swarm.add(field)
207
+
208
+ # move all adjacent fields to the set
209
+ loop do
210
+ to_add = current_swarm
211
+ .map { |f| GameRuleLogic.neighbours(board, f) }
212
+ .flatten
213
+ .select { |f| fields_to_check.include? f }
214
+ break if to_add.empty?
215
+ fields_to_check -= to_add
216
+ current_swarm += to_add
217
+ end
218
+
219
+ # keep trying to find bigger sets
220
+ if current_swarm.size > current_biggest_swarm.size
221
+ GameRuleLogic.greatest_swarm_from_fields(
222
+ board, fields_to_check, current_swarm
223
+ )
224
+ else
225
+ GameRuleLogic.greatest_swarm_from_fields(
226
+ board, fields_to_check, current_biggest_swarm
227
+ )
228
+ end
229
+ end
230
+ end
@@ -6,52 +6,51 @@ require_relative 'move'
6
6
  require_relative 'condition'
7
7
  require_relative 'field_type'
8
8
 
9
- # The state of a game, as received from the server.
9
+ # Ein Spielzustand. Wird vom Server an die Computerspieler übermittelt und enthält alles, was der Computerspieler wissen muss, um einen Zug zu machen.
10
+ #
11
+ # Um eine Liste der gerade möglichen Züge zu bekommen, gibt es die Methode {GameState#possible_moves}.
10
12
  class GameState
11
13
  # @!attribute [rw] turn
12
- # @return [Integer] turn number
14
+ # @return [Integer] Aktuelle Zugnummer (von 0 beginnend)
13
15
  attr_accessor :turn
14
16
  # @!attribute [rw] start_player_color
15
- # @return [PlayerColor] the start-player's color
17
+ # @return [PlayerColor] Die Farbe des Spielers, der den ersten Zug im Spiel machen darf.
16
18
  attr_accessor :start_player_color
17
19
  # @!attribute [rw] current_player_color
18
- # @return [PlayerColor] the current player's color
20
+ # @return [PlayerColor] Die Farbe des Spielers, der den nächsten Zug machen darf, der also gerade an der Reihe ist.
19
21
  attr_accessor :current_player_color
20
22
  # @!attribute [r] red
21
- # @return [Player] the red player
23
+ # @return [Player] Der rote Spieler
22
24
  attr_reader :red
23
25
  # @!attribute [r] blue
24
- # @return [Player] the blue player
26
+ # @return [Player] Der blaue Spieler
25
27
  attr_reader :blue
26
28
  # @!attribute [rw] board
27
- # @return [Board] the game's board
29
+ # @return [Board] Das aktuelle Spielbrett
28
30
  attr_accessor :board
29
31
  # @!attribute [rw] last_move
30
- # @return [Move] the last move performed
32
+ # @return [Move] Der zuletzt gemachte Zug (ist nil vor dem ersten Zug, also bei turn == 0)
31
33
  attr_accessor :last_move
32
34
  # @!attribute [rw] condition
33
- # @return [Condition] the winner and winning reason
35
+ # @return [Condition] Gewinner und Gewinngrund, falls das Spiel bereits entschieden ist, sonst nil.
34
36
  attr_accessor :condition
35
- # @!attribute [rw] has_to_play_card
36
- # @return [Boolean] true if the current player has to play a card
37
- attr_accessor :has_to_play_card
38
- alias has_to_play_card? has_to_play_card
39
37
 
40
- def field(index)
41
- board.field(index)
38
+ # Zugriff auf ein Feld des Spielbrettes. Siehe {Board#field}.
39
+ def field(x, y)
40
+ board.field(x, y)
42
41
  end
43
42
 
43
+ # Erstellt einen neuen Spielzustand.
44
44
  def initialize
45
45
  @current_player_color = PlayerColor::RED
46
46
  @start_player_color = PlayerColor::RED
47
47
  @board = Board.new
48
- @has_to_play_card = false
49
48
  @turn = 0
50
49
  end
51
50
 
52
- # adds a player to the gamestate
51
+ # Fügt einen Spieler zum Spielzustand hinzu.
53
52
  #
54
- # @param player [Player] the player, that will be added
53
+ # @param player [Player] Der hinzuzufügende Spieler.
55
54
  def add_player(player)
56
55
  if player.color == PlayerColor::RED
57
56
  @red = player
@@ -60,108 +59,58 @@ class GameState
60
59
  end
61
60
  end
62
61
 
63
- # gets the current player
64
- #
65
- # @return [Player] the current player
62
+ # @return [Player] Spieler, der gerade an der Reihe ist.
66
63
  def current_player
67
64
  return red if current_player_color == PlayerColor::RED
68
65
  return blue if current_player_color == PlayerColor::BLUE
69
66
  end
70
67
 
71
- # gets the other (not the current) player
72
- #
73
- # @return [Player] the other (not the current) player
68
+ # @return [Player] Spieler, der gerade nicht an der Reihe ist.
74
69
  def other_player
75
70
  return blue if current_player_color == PlayerColor::RED
76
71
  return red if current_player_color == PlayerColor::BLUE
77
72
  end
78
73
 
79
- # gets the other (not the current) player's color
80
- #
81
- # @return [PlayerColor] the other (not the current) player's color
74
+ # @return [PlayerColor] Farbe des Spielers, der gerade nicht an der Reihe ist.
82
75
  def other_player_color
83
76
  PlayerColor.opponent_color(current_player_color)
84
77
  end
85
78
 
86
- # gets the current round
87
- #
88
- # @return [Integer] the current round
79
+ # @return [Integer] Aktuelle Runde (von 0 beginnend).
89
80
  def round
90
81
  turn / 2
91
82
  end
92
83
 
93
- # performs a move on the gamestate
84
+ # Führt einen Zug auf dem Spielzustand aus. Das Spielbrett wird entsprechend modifiziert.
94
85
  #
95
- # @param move [Move] the move, that will be performed
96
- # @param player [Player] the player, who makes the move
97
- def perform!(move, player)
98
- move.actions.each do |action|
99
- action.perform!(self, player)
100
- end
86
+ # @param move [Move] Der auszuführende Zug.
87
+ def perform!(move)
88
+ move.perform!(self)
101
89
  end
102
90
 
103
- # has the game ended?
104
- #
105
- # @return [Boolean] true, if the game has allready ended
91
+ # @return [Boolean] true, falls das Spiel bereits geendet hat, false bei noch laufenden Spielen.
106
92
  def game_ended?
107
93
  !condition.nil?
108
94
  end
109
95
 
110
- # gets the game's winner
111
- #
112
- # @return [Player] the game's winner
96
+ # @return [Player] Der Spieler, der das Spiel gewonnen hat, falls dies schon entschieden ist. Sonst false.
113
97
  def winner
114
98
  condition.nil? ? nil : condition.winner
115
99
  end
116
100
 
117
- # gets the winning reason
118
- #
119
- # @return [String] the winning reason
101
+ # @return [String] Der Grund, warum das Spiel beendet wurde, nil falls das Spiel noch läuft.
120
102
  def winning_reason
121
103
  condition.nil? ? nil : condition.reason
122
104
  end
123
105
 
124
- # calculates a player's points based on the current gamestate
106
+ # Ermittelt die Punkte eines Spielers. Wenn das Spiel durch Erreichen des Rundenlimits beendet wird, hat der Spieler mit den meisten Punkten gewonnen.
125
107
  #
126
- # @param player [Player] the player, whos points to calculate
127
- # @return [Integer] the points of the player
108
+ # @param player [Player] Der Spieler, dessen Punkte berechnet werden sollen.
109
+ # @return [Integer] Die Punkte des Spielers, entspricht der Anzahl der Fische im größten Schwarm des Spielers.
128
110
  def points_for_player(player)
129
- player.index
130
- end
131
-
132
- # @return [Boolean] true if the given field is occupied by the other (not
133
- # current) player.
134
- def occupied_by_other_player?(field)
135
- field.index == other_player.index
111
+ GameRuleLogic.swarm_size(board, player)
136
112
  end
137
113
 
138
- # Searches backwards starting at the field with the given index. The field
139
- # at the given index is not considered. If no field of the given type exists,
140
- # nil is returned.
141
- #
142
- # @param [FieldType] type of the field to search for
143
- # @param [Integer] index before which field-index to start searching
144
- # @return [Field] the next field before the given index of given type
145
- def previous_field_of_type(type, index)
146
- return nil if index < 1
147
- return nil if index >= board.fields.size
148
- board.fields.slice(0..(index - 1)).reverse.find {|f| f.type == type}
149
- end
150
-
151
- # Searches forward starting at the field with the given index. The field
152
- # at the given index is not considered. If no field of the given type exists,
153
- # nil is returned.
154
- #
155
- # @param [FieldType] type of the field to search for
156
- # @param [Integer] index after which field-index to start searching
157
- # @return [Field] the next field after the given index of given type
158
- def next_field_of_type(type, index)
159
- return nil if index >= board.fields.size
160
- return nil if index < 0
161
- board.fields.slice((index + 1)..(board.fields.size - 1)).find {|f| f.type == type}
162
- end
163
-
164
- # Compared with other state.
165
114
  def ==(other)
166
115
  turn == other.turn &&
167
116
  start_player_color == other.start_player_color &&
@@ -170,126 +119,31 @@ class GameState
170
119
  blue == other.blue &&
171
120
  board == other.board &&
172
121
  lastMove == other.lastMove &&
173
- has_to_play_card == other.has_to_play_card &&
174
122
  condition == other.condition
175
123
  end
176
124
 
177
- # Create a deep copy of the gamestate. Can be used to perform moves on without
178
- # changing the original gamestate.
125
+ # Erzeugt eine Kopie des Spielzustandes. Änderungen an dieser Kopie beeinflussen den originalen Spielzustand nicht. Die Kopie kann also zum testen von Spielzügen genutzt werden.
179
126
  def deep_clone
180
127
  Marshal.load(Marshal.dump(self))
181
128
  end
182
129
 
183
- def set_last_action(action)
184
- return if action.instance_of? Skip
185
- current_player.last_non_skip_action = action
186
- end
187
-
188
- def current_field
189
- field(current_player.index)
190
- end
191
-
192
- def is_first(player)
193
- if PlayerColor.opponent_color(player.color) == PlayerColor::RED
194
- player.index > red.index
195
- else
196
- player.index > blue.index
197
- end
198
- end
199
-
200
- def is_second(player)
201
- !is_first(player)
202
- end
203
-
130
+ # Wechselt den Spieler, der aktuell an der Reihe ist.
204
131
  def switch_current_player
205
132
  current_player_color = other_player_color
206
133
  end
207
134
 
208
- def possible_moves
209
- found_moves = []
210
- if GameRules.is_valid_to_eat(self)[0]
211
- # Wenn ein Salat gegessen werden kann, muss auch ein Salat gegessen werden
212
- found_moves << Move.new([EatSalad.new])
213
- return found_moves
214
- end
215
- if GameRules.is_valid_to_exchange_carrots(self, 10)[0]
216
- found_moves << Move.new([ExchangeCarrots.new(10)])
217
- end
218
- if GameRules.is_valid_to_exchange_carrots(self, -10)[0]
219
- found_moves << Move.new([ExchangeCarrots.new(-10)])
220
- end
221
- if GameRules.is_valid_to_fall_back(self)[0]
222
- found_moves << Move.new([FallBack.new])
223
- end
224
- # Generiere mögliche Vorwärtszüge
225
- (1..(GameRules.calculate_movable_fields(self.current_player.carrots))).each do |distance|
226
- actions = []
227
- clone = self.deep_clone
228
- # Überprüfe ob Vorwärtszug möglich ist
229
- if GameRules.is_valid_to_advance(clone, distance)[0]
230
- try_advance = Advance.new(distance)
231
- try_advance.perform!(clone)
232
- actions << try_advance
233
- # überprüfe, ob eine Karte gespielt werden muss/kann
234
- if GameRules.must_play_card(clone)
235
- clone.check_for_playable_cards(actions).each do |card|
236
- found_moves << card # TODO: this is unexpected, rename or refactor
237
- end
238
- else
239
- # Füge möglichen Vorwärtszug hinzu
240
- found_moves << Move.new(actions)
241
- end
242
- end
243
- end
244
- if found_moves.empty?
245
- found_moves << Move.new([Skip.new])
246
- end
247
- found_moves
135
+ # @return [Array<Field>] Alle Felder mit Fischen des Spielers, der gerade an der Reihe ist.
136
+ def own_fields
137
+ board.fields_of_type(
138
+ PlayerColor.field_type(current_player_color)
139
+ )
248
140
  end
249
141
 
250
- # @return [Array[Move]] gibt liste von Zuegen zurueck, die gemacht werden koennen, vorausgesetzt die gegebene Liste von Aktionen wurde schon gemacht.
251
- def check_for_playable_cards(actions)
252
- found_card_playing_moves = []
253
- if current_player.must_play_card
254
- if GameRules.is_valid_to_play_eat_salad(self)[0]
255
- found_card_playing_moves << Move.new(actions + [Card.new(CardType::EAT_SALAD)])
256
- end
257
- [20, -20, 0].each do |carrots|
258
- if GameRules.is_valid_to_play_take_or_drop_carrots(self,carrots)[0]
259
- found_card_playing_moves << Move.new(actions + [Card.new(CardType::TAKE_OR_DROP_CARROTS, carrots)])
260
- end
261
- end
262
- if GameRules.is_valid_to_play_hurry_ahead(self)[0]
263
- actions_with_card_played = actions + [Card.new(CardType::HURRY_AHEAD)]
264
- # pruefe, ob auf Hasenfeld gelandet
265
- clone = self.deep_clone
266
- Card.new(CardType::HURRY_AHEAD).perform!(clone)
267
- moves = []
268
- if GameRules.must_play_card(clone)
269
- moves = clone.check_for_playable_cards(actions_with_card_played)
270
- end
271
- if moves.empty?
272
- found_card_playing_moves << Move.new(actions_with_card_played)
273
- else
274
- found_card_playing_moves += moves
275
- end
276
- end
277
- if GameRules.is_valid_to_play_fall_back(self)[0]
278
- actions_with_card_played = actions + [Card.new(CardType::FALL_BACK)]
279
- # pruefe, ob auf Hasenfeld gelandet
280
- clone = self.deep_clone
281
- Card.new(CardType::FALL_BACK).perform!(clone)
282
- moves = []
283
- if GameRules.must_play_card(clone)
284
- moves = clone.check_for_playable_cards(actions_with_card_played)
285
- end
286
- if moves.empty?
287
- found_card_playing_moves << Move.new(actions_with_card_played)
288
- else
289
- found_card_playing_moves += moves
290
- end
291
- end
292
- end
293
- found_card_playing_moves
142
+ # @return [Array<Move>] Alle regelkonformen Züge, die der aktuelle Spieler gerade machen könnte.
143
+ def possible_moves
144
+ own_fields.map do |f|
145
+ GameRuleLogic.possible_moves(board, f, current_player_color)
146
+ end.flatten
294
147
  end
148
+
295
149
  end