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.
- checksums.yaml +4 -4
- data/Dockerfile +3 -0
- data/README.md +64 -26
- data/example/client.rb +1 -71
- data/example/main.rb +1 -1
- data/lib/software_challenge_client.rb +4 -2
- data/lib/software_challenge_client/board.rb +66 -19
- data/lib/software_challenge_client/client_interface.rb +10 -5
- data/lib/software_challenge_client/condition.rb +2 -2
- data/lib/software_challenge_client/coordinates.rb +17 -0
- data/lib/software_challenge_client/debug_hint.rb +8 -4
- data/lib/software_challenge_client/direction.rb +53 -0
- data/lib/software_challenge_client/field.rb +26 -12
- data/lib/software_challenge_client/field_type.rb +25 -19
- data/lib/software_challenge_client/game_rule_logic.rb +230 -0
- data/lib/software_challenge_client/game_state.rb +45 -191
- data/lib/software_challenge_client/invalid_move_exception.rb +6 -8
- data/lib/software_challenge_client/line.rb +126 -0
- data/lib/software_challenge_client/line_direction.rb +15 -0
- data/lib/software_challenge_client/logging.rb +3 -2
- data/lib/software_challenge_client/move.rb +51 -38
- data/lib/software_challenge_client/network.rb +3 -1
- data/lib/software_challenge_client/player.rb +0 -39
- data/lib/software_challenge_client/player_color.rb +23 -13
- data/lib/software_challenge_client/protocol.rb +20 -83
- data/lib/software_challenge_client/runner.rb +2 -1
- data/lib/software_challenge_client/util/constants.rb +8 -5
- data/lib/software_challenge_client/version.rb +1 -1
- data/software_challenge_client.gemspec +2 -0
- metadata +24 -8
- data/lib/software_challenge_client/action.rb +0 -217
- data/lib/software_challenge_client/card_type.rb +0 -13
- data/lib/software_challenge_client/field_unavailable_exception.rb +0 -17
- 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
|
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]
|
11
|
-
# @return [Integer]
|
12
|
-
attr_reader :
|
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
|
18
|
-
|
19
|
-
|
20
|
-
@
|
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
|
-
|
24
|
-
|
25
|
-
|
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
|
-
|
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
|
-
#
|
5
|
-
#
|
6
|
-
#
|
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 :
|
9
|
-
new :
|
10
|
-
|
11
|
-
new :
|
12
|
-
|
13
|
-
|
14
|
-
#
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
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
|
-
#
|
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]
|
14
|
+
# @return [Integer] Aktuelle Zugnummer (von 0 beginnend)
|
13
15
|
attr_accessor :turn
|
14
16
|
# @!attribute [rw] start_player_color
|
15
|
-
# @return [PlayerColor]
|
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]
|
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]
|
23
|
+
# @return [Player] Der rote Spieler
|
22
24
|
attr_reader :red
|
23
25
|
# @!attribute [r] blue
|
24
|
-
# @return [Player]
|
26
|
+
# @return [Player] Der blaue Spieler
|
25
27
|
attr_reader :blue
|
26
28
|
# @!attribute [rw] board
|
27
|
-
# @return [Board]
|
29
|
+
# @return [Board] Das aktuelle Spielbrett
|
28
30
|
attr_accessor :board
|
29
31
|
# @!attribute [rw] last_move
|
30
|
-
# @return [Move]
|
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]
|
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
|
-
|
41
|
-
|
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
|
-
#
|
51
|
+
# Fügt einen Spieler zum Spielzustand hinzu.
|
53
52
|
#
|
54
|
-
# @param player [Player]
|
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
|
-
#
|
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
|
-
#
|
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
|
-
#
|
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
|
-
#
|
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
|
-
#
|
84
|
+
# Führt einen Zug auf dem Spielzustand aus. Das Spielbrett wird entsprechend modifiziert.
|
94
85
|
#
|
95
|
-
# @param move [Move]
|
96
|
-
|
97
|
-
|
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
|
-
#
|
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
|
-
#
|
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
|
-
#
|
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
|
-
#
|
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]
|
127
|
-
# @return [Integer]
|
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
|
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
|
-
#
|
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
|
-
|
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
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
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
|
251
|
-
def
|
252
|
-
|
253
|
-
|
254
|
-
|
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
|