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
@@ -3,11 +3,12 @@ require_relative 'board'
|
|
3
3
|
require_relative 'client_interface'
|
4
4
|
require_relative 'network'
|
5
5
|
|
6
|
+
# Klasse zum Starten einer neue Verbindung zum Spielserver und Verarbeiten der Nachrichten des Servers.
|
6
7
|
class Runner
|
7
8
|
include Logging
|
8
9
|
|
9
10
|
def initialize(host, port, client, reservation = nil)
|
10
|
-
logger.info 'Software Challenge
|
11
|
+
logger.info 'Software Challenge 2019'
|
11
12
|
logger.info 'Ruby Client'
|
12
13
|
logger.info "Host: #{host}"
|
13
14
|
logger.info "Port: #{port}"
|
@@ -1,6 +1,9 @@
|
|
1
|
-
# encoding:
|
2
|
-
#
|
1
|
+
# encoding: utf-8
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
# Konstanten zum aktuellen Spiel.
|
3
5
|
module Constants
|
4
|
-
ROUND_LIMIT =
|
5
|
-
SIZE =
|
6
|
-
|
6
|
+
ROUND_LIMIT = 30 # Rundenbegrenzung. Nach Ende der angegebenen Runde endet auch das Spiel.
|
7
|
+
SIZE = 10 # Die Größe des Spielbrettes (Breite und Höhe in Feldern).
|
8
|
+
GAME_IDENTIFIER = 'swc_2019_piranhas' # Der Identifikator des Spiels. Für die Kommunikation mit dem Spielserver.
|
9
|
+
end
|
@@ -18,6 +18,7 @@ Gem::Specification.new do |spec|
|
|
18
18
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
19
19
|
spec.require_paths = ["lib"]
|
20
20
|
|
21
|
+
spec.required_ruby_version = '>= 2.3'
|
21
22
|
spec.add_dependency 'typesafe_enum'
|
22
23
|
spec.add_dependency 'builder'
|
23
24
|
|
@@ -34,4 +35,5 @@ Gem::Specification.new do |spec|
|
|
34
35
|
spec.add_development_dependency 'pry'
|
35
36
|
spec.add_development_dependency 'pry-rescue'
|
36
37
|
spec.add_development_dependency 'pry-coolline'
|
38
|
+
spec.add_development_dependency 'pry-byebug'
|
37
39
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: software_challenge_client
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 19.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- 'kwollw '
|
@@ -10,7 +10,7 @@ authors:
|
|
10
10
|
autorequire:
|
11
11
|
bindir: exe
|
12
12
|
cert_chain: []
|
13
|
-
date:
|
13
|
+
date: 2018-09-25 00:00:00.000000000 Z
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: typesafe_enum
|
@@ -222,6 +222,20 @@ dependencies:
|
|
222
222
|
- - ">="
|
223
223
|
- !ruby/object:Gem::Version
|
224
224
|
version: '0'
|
225
|
+
- !ruby/object:Gem::Dependency
|
226
|
+
name: pry-byebug
|
227
|
+
requirement: !ruby/object:Gem::Requirement
|
228
|
+
requirements:
|
229
|
+
- - ">="
|
230
|
+
- !ruby/object:Gem::Version
|
231
|
+
version: '0'
|
232
|
+
type: :development
|
233
|
+
prerelease: false
|
234
|
+
version_requirements: !ruby/object:Gem::Requirement
|
235
|
+
requirements:
|
236
|
+
- - ">="
|
237
|
+
- !ruby/object:Gem::Version
|
238
|
+
version: '0'
|
225
239
|
description: ''
|
226
240
|
email:
|
227
241
|
- kwollw@users.noreply.github.com
|
@@ -237,6 +251,7 @@ files:
|
|
237
251
|
- ".ruby-version"
|
238
252
|
- AUTHORS
|
239
253
|
- CODE_OF_CONDUCT.md
|
254
|
+
- Dockerfile
|
240
255
|
- Gemfile
|
241
256
|
- Guardfile
|
242
257
|
- README.md
|
@@ -250,18 +265,19 @@ files:
|
|
250
265
|
- example/start.bat
|
251
266
|
- generate-authors.sh
|
252
267
|
- lib/software_challenge_client.rb
|
253
|
-
- lib/software_challenge_client/action.rb
|
254
268
|
- lib/software_challenge_client/board.rb
|
255
|
-
- lib/software_challenge_client/card_type.rb
|
256
269
|
- lib/software_challenge_client/client_interface.rb
|
257
270
|
- lib/software_challenge_client/condition.rb
|
271
|
+
- lib/software_challenge_client/coordinates.rb
|
258
272
|
- lib/software_challenge_client/debug_hint.rb
|
273
|
+
- lib/software_challenge_client/direction.rb
|
259
274
|
- lib/software_challenge_client/field.rb
|
260
275
|
- lib/software_challenge_client/field_type.rb
|
261
|
-
- lib/software_challenge_client/
|
262
|
-
- lib/software_challenge_client/game_rules.rb
|
276
|
+
- lib/software_challenge_client/game_rule_logic.rb
|
263
277
|
- lib/software_challenge_client/game_state.rb
|
264
278
|
- lib/software_challenge_client/invalid_move_exception.rb
|
279
|
+
- lib/software_challenge_client/line.rb
|
280
|
+
- lib/software_challenge_client/line_direction.rb
|
265
281
|
- lib/software_challenge_client/logging.rb
|
266
282
|
- lib/software_challenge_client/move.rb
|
267
283
|
- lib/software_challenge_client/network.rb
|
@@ -284,7 +300,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
284
300
|
requirements:
|
285
301
|
- - ">="
|
286
302
|
- !ruby/object:Gem::Version
|
287
|
-
version: '
|
303
|
+
version: '2.3'
|
288
304
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
289
305
|
requirements:
|
290
306
|
- - ">="
|
@@ -292,7 +308,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
292
308
|
version: '0'
|
293
309
|
requirements: []
|
294
310
|
rubyforge_project:
|
295
|
-
rubygems_version: 2.6.
|
311
|
+
rubygems_version: 2.6.11
|
296
312
|
signing_key:
|
297
313
|
specification_version: 4
|
298
314
|
summary: This gem provides functions to build a client for the coding competition
|
@@ -1,217 +0,0 @@
|
|
1
|
-
# encoding: utf-8
|
2
|
-
|
3
|
-
# An action is a part of a move. A move can have multiple actions. The specific
|
4
|
-
# actions are inherited from this Action class which should be considered
|
5
|
-
# abstract/interface.
|
6
|
-
class Action
|
7
|
-
# @return [ActionType] Type of the action.
|
8
|
-
def type
|
9
|
-
raise 'must be overridden'
|
10
|
-
end
|
11
|
-
|
12
|
-
def ==(_other)
|
13
|
-
raise 'must be overridden'
|
14
|
-
end
|
15
|
-
|
16
|
-
def perform!(_gamestate)
|
17
|
-
raise 'must be overridden'
|
18
|
-
end
|
19
|
-
|
20
|
-
# Helper to make raising InvalidMoveExceptions easier. It is defined in the
|
21
|
-
# Action class instead of the Move class because performing individual actions
|
22
|
-
# normally trigger invalid moves, not the move itself.
|
23
|
-
#
|
24
|
-
# @param message [String] Message why the move is invalid.
|
25
|
-
# @return Nothing. Raises an exception.
|
26
|
-
def invalid(message)
|
27
|
-
raise InvalidMoveException.new(message, self)
|
28
|
-
end
|
29
|
-
end
|
30
|
-
|
31
|
-
# Ein Vorwärtszug, um spezifizierte Distanz. Verbrauchte Karroten werden mit k =
|
32
|
-
# (distance * (distance + 1)) / 2 berechnet (Gaußsche Summe)
|
33
|
-
class Advance < Action
|
34
|
-
attr_reader :distance
|
35
|
-
|
36
|
-
def initialize(distance)
|
37
|
-
@distance = distance
|
38
|
-
end
|
39
|
-
|
40
|
-
# Perform the action.
|
41
|
-
#
|
42
|
-
# @param gamestate [GameState] The game state on which the action will be
|
43
|
-
# performed. Performing may change the game state. The action is performed for
|
44
|
-
# the current player of the game state.
|
45
|
-
def perform!(gamestate)
|
46
|
-
valid, message = GameRules.is_valid_to_advance(gamestate, distance)
|
47
|
-
invalid(message) unless valid
|
48
|
-
# perform state changes
|
49
|
-
required_carrots = distance * (distance + 1) / 2
|
50
|
-
gamestate.current_player.carrots -= required_carrots
|
51
|
-
gamestate.current_player.index += distance
|
52
|
-
if gamestate.current_field.type == FieldType::HARE
|
53
|
-
gamestate.current_player.must_play_card = true
|
54
|
-
end
|
55
|
-
end
|
56
|
-
|
57
|
-
def type
|
58
|
-
:advance
|
59
|
-
end
|
60
|
-
|
61
|
-
def ==(other)
|
62
|
-
other.type == type && other.distance == distance
|
63
|
-
end
|
64
|
-
end
|
65
|
-
|
66
|
-
# Play a card.
|
67
|
-
class Card < Action
|
68
|
-
# only for type TAKE_OR_DROP_CARROTS
|
69
|
-
attr_reader :value
|
70
|
-
|
71
|
-
attr_reader :card_type
|
72
|
-
|
73
|
-
def initialize(card_type, value = 0)
|
74
|
-
@card_type = card_type
|
75
|
-
@value = value
|
76
|
-
end
|
77
|
-
|
78
|
-
# (see Advance#perform!)
|
79
|
-
def perform!(gamestate)
|
80
|
-
gamestate.current_player.must_play_card = false
|
81
|
-
case card_type
|
82
|
-
when CardType::EAT_SALAD
|
83
|
-
valid, message = GameRules.is_valid_to_play_eat_salad(gamestate)
|
84
|
-
invalid("Das Ausspielen der EAT_SALAD Karte ist nicht möglich. " + message) unless valid
|
85
|
-
gamestate.current_player.salads -= 1
|
86
|
-
if gamestate.is_first(gamestate.current_player)
|
87
|
-
gamestate.current_player.carrots += 10
|
88
|
-
else
|
89
|
-
gamestate.current_player.carrots += 20
|
90
|
-
end
|
91
|
-
when CardType::FALL_BACK
|
92
|
-
valid, message = GameRules.is_valid_to_play_fall_back(gamestate)
|
93
|
-
invalid("Das Ausspielen der FALL_BACK Karte ist nicht möglich. " + message) unless valid
|
94
|
-
gamestate.current_player.index = gamestate.other_player.index - 1
|
95
|
-
if gamestate.field(gamestate.current_player.index).type == FieldType::HARE
|
96
|
-
gamestate.current_player.must_play_card = true
|
97
|
-
end
|
98
|
-
when CardType::HURRY_AHEAD
|
99
|
-
valid, message = GameRules.is_valid_to_play_hurry_ahead(gamestate)
|
100
|
-
invalid("Das Ausspielen der HURRY_AHEAD Karte ist nicht möglich. " + message) unless valid
|
101
|
-
gamestate.current_player.index = gamestate.other_player.index + 1
|
102
|
-
if gamestate.field(gamestate.current_player.index).type == FieldType::HARE
|
103
|
-
gamestate.current_player.must_play_card = true
|
104
|
-
end
|
105
|
-
when CardType::TAKE_OR_DROP_CARROTS
|
106
|
-
valid, message = GameRules.is_valid_to_play_take_or_drop_carrots(gamestate, value)
|
107
|
-
invalid("Das Ausspielen der TAKE_OR_DROP_CARROTS Karte ist nicht möglich. " + message) unless valid
|
108
|
-
gamestate.current_player.carrots += value
|
109
|
-
else
|
110
|
-
raise "Unknown card type #{card_type.inspect}!"
|
111
|
-
end
|
112
|
-
gamestate.set_last_action(self)
|
113
|
-
gamestate.current_player.cards.delete(card_type)
|
114
|
-
end
|
115
|
-
|
116
|
-
def type
|
117
|
-
:card
|
118
|
-
end
|
119
|
-
|
120
|
-
def ==(other)
|
121
|
-
other.card_type == card_type &&
|
122
|
-
(card_type != CardType::TAKE_OR_DROP_CARROTS || (other.value == value))
|
123
|
-
end
|
124
|
-
end
|
125
|
-
|
126
|
-
# Ein Aussetzzug. Ist nur erlaubt, sollten keine anderen Züge möglich sei
|
127
|
-
class Skip < Action
|
128
|
-
def initialize()
|
129
|
-
end
|
130
|
-
|
131
|
-
def type
|
132
|
-
:skip
|
133
|
-
end
|
134
|
-
|
135
|
-
def ==(other)
|
136
|
-
other.type == type
|
137
|
-
end
|
138
|
-
|
139
|
-
def perform!(_gamestate)
|
140
|
-
# does nothing
|
141
|
-
end
|
142
|
-
end
|
143
|
-
|
144
|
-
# Eine Salatessen-Aktion. Kann nur auf einem Salatfeld ausgeführt werden. Muss ausgeführt werden,
|
145
|
-
# ein Salatfeld betreten wird. Nachdem die Aktion ausgefürht wurde, muss das Salatfeld verlassen
|
146
|
-
# werden, oder es muss ausgesetzt werden.
|
147
|
-
# Duch eine Salatessen-Aktion wird ein Salat verbraucht und es werden je nachdem ob der Spieler führt
|
148
|
-
# oder nicht 10 oder 30 Karotten aufgenommen.
|
149
|
-
class EatSalad < Action
|
150
|
-
def initialize()
|
151
|
-
end
|
152
|
-
|
153
|
-
def type
|
154
|
-
:eat_salad
|
155
|
-
end
|
156
|
-
|
157
|
-
def ==(other)
|
158
|
-
other.type == type
|
159
|
-
end
|
160
|
-
|
161
|
-
def perform!(gamestate)
|
162
|
-
valid, msg = GameRules.is_valid_to_eat(gamestate)
|
163
|
-
if valid
|
164
|
-
gamestate.current_player.salads -= 1
|
165
|
-
if gamestate.is_first(gamestate.current_player)
|
166
|
-
gamestate.current_player.carrots += 10
|
167
|
-
else
|
168
|
-
gamestate.current_player.carrots += 30
|
169
|
-
end
|
170
|
-
gamestate.set_last_action(self)
|
171
|
-
else
|
172
|
-
invalid(msg)
|
173
|
-
end
|
174
|
-
end
|
175
|
-
end
|
176
|
-
|
177
|
-
# Karottentauschaktion. Es können auf einem Karottenfeld 10 Karotten abgegeben oder aufgenommen werden.
|
178
|
-
# Dies kann beliebig oft hintereinander ausgeführt werden.
|
179
|
-
class ExchangeCarrots < Action
|
180
|
-
attr_accessor :value
|
181
|
-
|
182
|
-
def initialize(value)
|
183
|
-
@value = value
|
184
|
-
end
|
185
|
-
|
186
|
-
def perform!(gamestate)
|
187
|
-
valid, message = GameRules.is_valid_to_exchange_carrots(gamestate, value)
|
188
|
-
invalid("Es können nicht #{value} Karotten aufgenommen werden. " + message) unless valid
|
189
|
-
gamestate.current_player.carrots += value
|
190
|
-
gamestate.set_last_action(self)
|
191
|
-
end
|
192
|
-
|
193
|
-
def type
|
194
|
-
:exchange_carrots
|
195
|
-
end
|
196
|
-
|
197
|
-
def ==(other)
|
198
|
-
other.type == type && other.value == value
|
199
|
-
end
|
200
|
-
end
|
201
|
-
|
202
|
-
class FallBack < Action
|
203
|
-
def perform!(gamestate)
|
204
|
-
valid, message = GameRules.is_valid_to_fall_back(gamestate)
|
205
|
-
raise InvalidMoveException("Es kann gerade kein Rückzug gemacht werden. " + message) unless valid
|
206
|
-
gamestate.current_player.index = gamestate.previous_field_of_type(FieldType::HEDGEHOG, gamestate.current_player.index).index
|
207
|
-
gamestate.set_last_action(self)
|
208
|
-
end
|
209
|
-
|
210
|
-
def type
|
211
|
-
:fall_back
|
212
|
-
end
|
213
|
-
|
214
|
-
def ==(other)
|
215
|
-
other.type == type
|
216
|
-
end
|
217
|
-
end
|
@@ -1,13 +0,0 @@
|
|
1
|
-
# encoding: utf-8
|
2
|
-
|
3
|
-
require 'typesafe_enum'
|
4
|
-
class CardType < TypesafeEnum::Base
|
5
|
-
# Nehme Karotten auf, oder leg sie ab
|
6
|
-
new :TAKE_OR_DROP_CARROTS
|
7
|
-
# Iß sofort einen Salat
|
8
|
-
new :EAT_SALAD
|
9
|
-
# Falle eine Position zurück
|
10
|
-
new :FALL_BACK
|
11
|
-
# Rücke eine Position vor
|
12
|
-
new :HURRY_AHEAD
|
13
|
-
end
|
@@ -1,17 +0,0 @@
|
|
1
|
-
# encoding: UTF-8
|
2
|
-
# Exception indicating that a requested field does not exist.
|
3
|
-
class FieldUnavailableException < StandardError
|
4
|
-
# @!attribute [r] x
|
5
|
-
# @return [Integer] the X-coordinate of the requested field.
|
6
|
-
attr_reader :x
|
7
|
-
|
8
|
-
# @!attribute [r] y
|
9
|
-
# @return [Integer] the Y-coordinate of the requested field.
|
10
|
-
attr_reader :y
|
11
|
-
|
12
|
-
def initialize(x, y)
|
13
|
-
super("Field with coordinates (#{x},#{y}) is not available.")
|
14
|
-
@x = x
|
15
|
-
@y = y
|
16
|
-
end
|
17
|
-
end
|
@@ -1,376 +0,0 @@
|
|
1
|
-
# coding: utf-8
|
2
|
-
|
3
|
-
require_relative 'field_type'
|
4
|
-
require_relative 'card_type'
|
5
|
-
|
6
|
-
# All methods which define the game rules. Needed for checking validity of moves
|
7
|
-
# and performing them.
|
8
|
-
class GameRules
|
9
|
-
|
10
|
-
# Berechnet wie viele Karotten für einen Zug der länge
|
11
|
-
# <code>moveCount</code> benötigt werden.
|
12
|
-
#
|
13
|
-
# @param moveCount Anzahl der Felder, um die bewegt wird
|
14
|
-
# @return Anzahl der benötigten Karotten
|
15
|
-
def self.calculate_carrots(moveCount)
|
16
|
-
(moveCount * (moveCount + 1)) / 2
|
17
|
-
end
|
18
|
-
|
19
|
-
# Berechnet, wieviele Züge mit <code>carrots</code> Karotten möglich sind.
|
20
|
-
#
|
21
|
-
# @param carrots maximal ausgegebene Karotten
|
22
|
-
# @return Felder um die maximal bewegt werden kann
|
23
|
-
def self.calculate_movable_fields(carrots)
|
24
|
-
# bei 30 Runden koennen nur 990 Karotten gesammelt werden
|
25
|
-
return 44 if carrots >= 990
|
26
|
-
return 0 if carrots < 1
|
27
|
-
(Math.sqrt(2 * carrots + 1/4) - 1/2).round
|
28
|
-
end
|
29
|
-
|
30
|
-
# Überprüft <code>Advance</code> Aktionen auf ihre Korrektheit. Folgende
|
31
|
-
# Spielregeln werden beachtet:
|
32
|
-
#
|
33
|
-
# - Der Spieler muss genügend Karotten für den Zug besitzen
|
34
|
-
# - Wenn das Ziel erreicht wird, darf der Spieler nach dem Zug maximal 10 Karotten übrig haben
|
35
|
-
# - Man darf nicht auf Igelfelder ziehen
|
36
|
-
# - Salatfelder dürfen nur betreten werden, wenn man noch Salate essen muss
|
37
|
-
# - Hasenfelder dürfen nur betreten werden, wenn man noch Hasenkarten ausspielen kann
|
38
|
-
#
|
39
|
-
# @param state GameState
|
40
|
-
# @param distance relativer Abstand zur aktuellen Position des Spielers
|
41
|
-
# @return [true, ''] falls ein Vorwärtszug möglich ist, [false, M] falls nicht, wobei M ein String mit einer Begruendung ist
|
42
|
-
def self.is_valid_to_advance(state, distance)
|
43
|
-
return false, 'Ein Vorwärtszug benötigt eine Mindestdistanz von einem Feld.' if distance <= 0
|
44
|
-
player = state.current_player
|
45
|
-
return false, 'Es muss ein Salat gegessen werden, bevor ein Vorwärtszug gemacht werden kann.' if must_eat_salad(state)
|
46
|
-
required_carrots = GameRules.calculate_carrots(distance)
|
47
|
-
return false, "Nicht genug Karotten, um #{distance} Felder vorwärts zu ziehen (Vorrat: #{player.carrots}, benötigt: #{required_carrots}" if (required_carrots > player.carrots)
|
48
|
-
new_position = player.index + distance
|
49
|
-
return false, 'Zielfeld wird von anderem Spieler besetzt.' if state.occupied_by_other_player?(state.field(new_position))
|
50
|
-
case state.field(new_position).type
|
51
|
-
when FieldType::INVALID
|
52
|
-
return false, "Zielfeld #{new_position} ist nicht vorhanden."
|
53
|
-
when FieldType::SALAD
|
54
|
-
return false, 'Ohne Salat darf ein Salatfeld nicht betreten werden.' if player.salads < 1
|
55
|
-
when FieldType::HARE
|
56
|
-
state_after_advance = state.deep_clone
|
57
|
-
state_after_advance.set_last_action(Advance.new(distance))
|
58
|
-
state_after_advance.current_player.index = new_position
|
59
|
-
state_after_advance.current_player.carrots -= required_carrots
|
60
|
-
return false, 'Auf ein Hasenfeld darf nur gezogen werden, wenn eine Karte gespielt werden kann' unless can_play_any_card(state_after_advance)
|
61
|
-
when FieldType::GOAL
|
62
|
-
carrotsLeft = player.carrots - required_carrots
|
63
|
-
return false, "Auf das Zielfeld darf nur mit maximal 10 Karotten gezogen werden (es sind aber #{carrotsLeft} bei Erreichen des Zielfeldes)." unless carrotsLeft <= 10
|
64
|
-
return false, "Auf das Zielfeld darf nur ohne Salate gezogen werden (es sind aber #{player.salads} übrig)." unless player.salads == 0
|
65
|
-
when FieldType::HEDGEHOG
|
66
|
-
return false, 'Auf ein Igelfeld darf nicht vorwärts gezogen werden.'
|
67
|
-
when FieldType::CARROT, FieldType::POSITION_1, FieldType::START, FieldType::POSITION_2
|
68
|
-
return true, ''
|
69
|
-
else
|
70
|
-
raise "Unknown Type #{state.field(new_position).type.inspect}"
|
71
|
-
end
|
72
|
-
return true, ''
|
73
|
-
end
|
74
|
-
|
75
|
-
# Überprüft, ob ein Spieler aussetzen darf. Er darf dies, wenn kein anderer Zug möglich ist.
|
76
|
-
# @param state GameState
|
77
|
-
# @return true, falls der derzeitige Spieler keine andere Aktion machen kann.
|
78
|
-
def self.is_valid_to_skip(state)
|
79
|
-
return !GameRules.can_do_anything(state)
|
80
|
-
end
|
81
|
-
|
82
|
-
# Überprüft, ob ein Spieler einen Zug (keinen Aussetzug)
|
83
|
-
# @param state GameState
|
84
|
-
# @return true, falls ein Zug möglich ist.
|
85
|
-
def self.can_do_anything(state)
|
86
|
-
return can_play_any_card(state) || is_valid_to_fall_back(state)[0] ||
|
87
|
-
is_valid_to_exchange_carrots(state, 10)[0] ||
|
88
|
-
is_valid_to_exchange_carrots(state, -10)[0] ||
|
89
|
-
is_valid_to_eat(state)[0] || can_advance_to_any_field(state)
|
90
|
-
end
|
91
|
-
|
92
|
-
# Überprüft ob der derzeitige Spieler zu irgendeinem Feld einen Vorwärtszug machen kann.
|
93
|
-
# @param state GameState
|
94
|
-
# @return true, falls der Spieler irgendeinen Vorwärtszug machen kann
|
95
|
-
def self.can_advance_to_any_field(state)
|
96
|
-
fields = calculate_movable_fields(state.getCurrentPlayer().getCarrots())
|
97
|
-
(0..fields).to_a.any? do |i|
|
98
|
-
is_valid_to_advance(state, i)[0]
|
99
|
-
end
|
100
|
-
end
|
101
|
-
|
102
|
-
# Überprüft <code>EatSalad</code> Züge auf Korrektheit. Um einen Salat
|
103
|
-
# zu verzehren muss der Spieler sich:
|
104
|
-
#
|
105
|
-
# - auf einem Salatfeld befinden
|
106
|
-
# - noch mindestens einen Salat besitzen
|
107
|
-
# - vorher kein Salat auf diesem Feld verzehrt wurde
|
108
|
-
#
|
109
|
-
# @param state GameState
|
110
|
-
# @return [true, ''], falls ein Salad gegessen werden darf, [false, M] falls nicht, wobei M ein String mit dem Grund ist
|
111
|
-
def self.is_valid_to_eat(state)
|
112
|
-
return false, 'Salate dürfen nur auf Salatfeldern gegessen werden.' unless state.current_field.type == FieldType::SALAD
|
113
|
-
return false, 'Spieler hat keine Salate mehr zum Essen.' if state.current_player.salads < 1
|
114
|
-
return false, 'Spieler muss das Feld verlassen.' if player_must_advance(state)
|
115
|
-
return true, ''
|
116
|
-
end
|
117
|
-
|
118
|
-
# Überprüft ab der derzeitige Spieler im nächsten Zug einen Vorwärtszug machen muss.
|
119
|
-
# @param state GameState
|
120
|
-
# @return true, falls der derzeitige Spieler einen Vorwärtszug gemacht werden muss
|
121
|
-
def self.player_must_advance(state)
|
122
|
-
player = state.current_player
|
123
|
-
type = state.board.field(player.index).type
|
124
|
-
|
125
|
-
return true if (type == FieldType::HEDGEHOG || type == FieldType::START)
|
126
|
-
|
127
|
-
last_action = state.current_player.last_non_skip_action
|
128
|
-
|
129
|
-
if (!last_action.nil?)
|
130
|
-
if (last_action.instance_of? EatSalad)
|
131
|
-
return true
|
132
|
-
elsif (last_action.instance_of? Card)
|
133
|
-
# the player has to leave a rabbit field in next turn
|
134
|
-
if (last_action.type == CardType::EAT_SALAD || last_action.type == CardType::TAKE_OR_DROP_CARROTS)
|
135
|
-
return true
|
136
|
-
end
|
137
|
-
end
|
138
|
-
end
|
139
|
-
|
140
|
-
return false
|
141
|
-
end
|
142
|
-
|
143
|
-
# Überprüft ob der derzeitige Spieler 10 Karotten nehmen oder abgeben kann.
|
144
|
-
# @param state GameState
|
145
|
-
# @param n 10 oder -10 je nach Fragestellung
|
146
|
-
# @return [true, ''], falls die durch n spezifizierte Aktion möglich ist, [false, M] falls nicht, wobei M ein String mit dem Grund ist
|
147
|
-
def self.is_valid_to_exchange_carrots(state, n)
|
148
|
-
player = state.current_player
|
149
|
-
return false, "Karotten können nur auf einem Karottenfeld #{n > 0 ? 'genommen' : 'abgegeben'} werden" if state.board.field(player.index).type != FieldType::CARROT
|
150
|
-
return true, '' if n == 10
|
151
|
-
return false, 'Gültige Karottenzahlen sind 10 und -10.' unless n == -10
|
152
|
-
return false, "Spieler hat keine 10 Karotten zum Abgeben (er hat #{player.carrots})." if player.carrots < 10
|
153
|
-
return true, ''
|
154
|
-
end
|
155
|
-
|
156
|
-
# Überprüft <code>FallBack</code> Züge auf Korrektheit
|
157
|
-
#
|
158
|
-
# @param state GameState
|
159
|
-
# @return [true, ''], falls der currentPlayer einen Rückzug machen darf, [false, M] falls nicht, wobei M ein String mit dem Grund ist.
|
160
|
-
def self.is_valid_to_fall_back(state)
|
161
|
-
return false, 'Spieler muss einen Salat fressen.' if must_eat_salad(state)
|
162
|
-
target_field = state.previous_field_of_type(
|
163
|
-
FieldType::HEDGEHOG, state.current_player.index
|
164
|
-
)
|
165
|
-
return false, 'Es gibt kein Igelfeld hinter dem Spieler.' if target_field.nil?
|
166
|
-
return false, 'Das Igelfeld hinter dem Spieler ist besetzt.' if state.occupied_by_other_player?(target_field)
|
167
|
-
return true, ''
|
168
|
-
end
|
169
|
-
|
170
|
-
# Überprüft ob der derzeitige Spieler die <code>FALL_BACK</code> Karte spielen darf.
|
171
|
-
# @param state GameState
|
172
|
-
# @return [true, ''], falls die <code>FALL_BACK</code> Karte gespielt werden darf, [false, M] falls nicht, wobei M ein String mit dem Grund ist.
|
173
|
-
def self.is_valid_to_play_fall_back(state)
|
174
|
-
player = state.current_player
|
175
|
-
return false, 'Spieler muss einen Vorwärtszug machen.' if player_must_advance(state)
|
176
|
-
return false, 'Karten können nur auf Hasenfeldern gespielt werden.' unless state.current_field.type == FieldType::HARE
|
177
|
-
return false, 'Nur der erste Spieler darf die FALL_BACK Karte spielen.' unless state.is_first(player)
|
178
|
-
return false, 'Spieler besitzt die Karte FALL_BACK nicht.' unless player.owns_card_of_type(CardType::FALL_BACK)
|
179
|
-
|
180
|
-
next_pos = state.other_player.index - 1
|
181
|
-
|
182
|
-
case state.field(next_pos).type
|
183
|
-
when FieldType::INVALID
|
184
|
-
return false, 'Durch Spielen der FALL_BACK Karte darf man nicht auf einem nicht vorhandenen Feld landen (also vor dem Start).'
|
185
|
-
when FieldType::HEDGEHOG
|
186
|
-
return false, 'Durch Spielen der FALL_BACK Karte darf man nicht auf einem Igelfeld landen.'
|
187
|
-
when FieldType::SALAD
|
188
|
-
return false, 'Spieler käme durch Spielen der FALL_BACK Kart auf ein Salatfeld, hat aber keine Salate.' if player.salads < 1
|
189
|
-
when FieldType::HARE
|
190
|
-
state_after_card_played = state.deep_clone
|
191
|
-
state_after_card_played.set_last_action(Card.new(CardType::FALL_BACK))
|
192
|
-
state_after_card_played.current_player.cards.delete(CardType::FALL_BACK)
|
193
|
-
return false, 'Spieler käme durch Spielen der FALL_BACK Kart auf ein Hasenfeld, kann aber dann keine weitere Karte mehr spielen.' unless can_play_any_card(state_after_card_played)
|
194
|
-
when FieldType::START
|
195
|
-
when FieldType::CARROT
|
196
|
-
when FieldType::POSITION_1
|
197
|
-
when FieldType::POSITION_2
|
198
|
-
return true, ''
|
199
|
-
when FieldType::GOAL
|
200
|
-
raise 'Player got onto goal by playing a fall back card. This should never happen.'
|
201
|
-
else
|
202
|
-
raise "Unknown Type #{state.field(next_pos).type.inspect}"
|
203
|
-
end
|
204
|
-
return true, ''
|
205
|
-
end
|
206
|
-
|
207
|
-
# Überprüft ob der derzeitige Spieler die <code>HURRY_AHEAD</code> Karte spielen darf.
|
208
|
-
# @param state GameState
|
209
|
-
# @return [true, ''], falls die <code>HURRY_AHEAD</code> Karte gespielt werden darf, [false, M], falls nicht, wobei M ein String mit dem Grund ist.
|
210
|
-
def self.is_valid_to_play_hurry_ahead(state)
|
211
|
-
player = state.current_player
|
212
|
-
return false, 'Spieler muss einen Vorwärtszug machen.' if player_must_advance(state)
|
213
|
-
return false, 'Karten können nur auf Hasenfeldern gespielt werden.' unless state.current_field.type == FieldType::HARE
|
214
|
-
return false, 'Nur der zweite Spieler darf die HURRY_AHEAD Karte spielen.' unless state.is_second(player)
|
215
|
-
return false, 'Spieler besitzt die Karte HURRY_AHEAD nicht.' unless player.owns_card_of_type(CardType::HURRY_AHEAD)
|
216
|
-
|
217
|
-
o = state.other_player
|
218
|
-
next_pos = o.index + 1
|
219
|
-
|
220
|
-
case state.field(next_pos).type
|
221
|
-
when FieldType::INVALID
|
222
|
-
return false, 'Durch Spielen der HURRY_AHEAD Karte darf man nicht auf einem nicht vorhandenen Feld landen (also nach dem Ziel).'
|
223
|
-
when FieldType::HEDGEHOG
|
224
|
-
return false, 'Durch Spielen der HURRY_AHEAD Karte darf man nicht auf einem Igelfeld landen.'
|
225
|
-
when FieldType::SALAD
|
226
|
-
return false, 'Spieler käme durch Spielen der HURRY_AHEAD Kart auf ein Salatfeld, hat aber keine Salate.' if player.salads < 1
|
227
|
-
when FieldType::HARE
|
228
|
-
state_after_card_played = state.deep_clone
|
229
|
-
state_after_card_played.set_last_action(Card.new(CardType::HURRY_AHEAD))
|
230
|
-
state_after_card_played.current_player.cards.delete(CardType::HURRY_AHEAD)
|
231
|
-
return false, 'Spieler käme durch Spielen der HURRY_AHEAD Kart auf ein Hasenfeld, kann aber dann keine weitere Karte mehr spielen.' unless can_play_any_card(state_after_card_played)
|
232
|
-
when FieldType::GOAL
|
233
|
-
return false, 'Spieler käme durch Spielen der HURRY_AHEAD Kart ins Ziel, darf es aber nicht betreten (entweder noch Salate oder mehr als 10 Karotten).' unless can_enter_goal(state)
|
234
|
-
when FieldType::CARROT
|
235
|
-
when FieldType::POSITION_1
|
236
|
-
when FieldType::POSITION_2
|
237
|
-
return true, ''
|
238
|
-
when FieldType::START
|
239
|
-
raise 'Player got onto start field by playing a hurry ahead card. This should never happen.'
|
240
|
-
else
|
241
|
-
raise "Unknown Type #{state.field(next_pos).type.inspect}"
|
242
|
-
end
|
243
|
-
return true, ''
|
244
|
-
end
|
245
|
-
|
246
|
-
# Überprüft ob der derzeitige Spieler die <code>TAKE_OR_DROP_CARROTS</code> Karte spielen darf.
|
247
|
-
# @param state GameState
|
248
|
-
# @param n 20 für nehmen, -20 für abgeben, 0 für nichts tun
|
249
|
-
# @return [true, ''], falls die <code>TAKE_OR_DROP_CARROTS</code> Karte gespielt werden darf, [false, M], falls nicht, wobei M ein String mit dem Grund ist.
|
250
|
-
def self.is_valid_to_play_take_or_drop_carrots(state, n)
|
251
|
-
player = state.current_player
|
252
|
-
return false, 'Spieler muss einen Vorwärtszug machen.' if player_must_advance(state)
|
253
|
-
return false, 'Karten können nur auf Hasenfeldern gespielt werden.' unless state.current_field.type == FieldType::HARE
|
254
|
-
return false, 'Spieler besitzt die Karte TAKE_OR_DROP_CARROTS nicht.' unless player.owns_card_of_type(CardType::TAKE_OR_DROP_CARROTS)
|
255
|
-
return false, "#{n} ist keine erlaubte Anzahl beim Spielen der Karte TAKE_OR_DROP_CARROTS (erlaubt sind 20, -20 und 0)." unless [20, -20, 0].include?(n)
|
256
|
-
return true, '' if n >= 0
|
257
|
-
# at this point, n has to be -20
|
258
|
-
return false, "Spieler hat keine 20 Karotten zum Abgeben (er hat #{player.carrots})." if player.carrots < 20
|
259
|
-
return true, ''
|
260
|
-
end
|
261
|
-
|
262
|
-
# Überprüft ob der derzeitige Spieler die <code>EAT_SALAD</code> Karte spielen darf.
|
263
|
-
# @param state GameState
|
264
|
-
# @return (true, ''), falls die <code>EAT_SALAD</code> Karte gespielt werden darf, (false, M) falls nicht, wobei M ein String mit dem Grund ist
|
265
|
-
def self.is_valid_to_play_eat_salad(state)
|
266
|
-
player = state.current_player
|
267
|
-
return false, 'Es muss vorwärts gezogen werden.' if player_must_advance(state)
|
268
|
-
return false, 'Karten können nur auf Hasenfeldern gespielt werden.' unless state.current_field.type == FieldType::HARE
|
269
|
-
return false, 'Spieler besitzt die Karte nicht.' unless player.owns_card_of_type(CardType::EAT_SALAD)
|
270
|
-
return false, 'Spieler hat keine Salate zum Essen' if player.salads < 1
|
271
|
-
return true, ''
|
272
|
-
end
|
273
|
-
|
274
|
-
# Überprüft ob der derzeitige Spieler irgendeine Karte spielen kann.
|
275
|
-
# TAKE_OR_DROP_CARROTS wird nur mit 20 überprüft
|
276
|
-
# @param state GameState
|
277
|
-
# @return true, falls das Spielen einer Karte möglich ist
|
278
|
-
def self.can_play_any_card(state)
|
279
|
-
valid = false
|
280
|
-
player = state.current_player
|
281
|
-
|
282
|
-
player.cards.any? do |card|
|
283
|
-
case card
|
284
|
-
when CardType::EAT_SALAD
|
285
|
-
is_valid_to_play_eat_salad(state)[0]
|
286
|
-
when CardType::FALL_BACK
|
287
|
-
is_valid_to_play_fall_back(state)[0]
|
288
|
-
when CardType::HURRY_AHEAD
|
289
|
-
is_valid_to_play_hurry_ahead(state)[0]
|
290
|
-
when CardType::TAKE_OR_DROP_CARROTS
|
291
|
-
is_valid_to_play_take_or_drop_carrots(state, 20)[0]
|
292
|
-
else
|
293
|
-
raise "Unknown CardType " + card
|
294
|
-
end
|
295
|
-
end
|
296
|
-
end
|
297
|
-
|
298
|
-
# Überprüft ob der derzeitige Spieler die Karte spielen kann.
|
299
|
-
# @param state
|
300
|
-
# @param c Karte die gespielt werden soll
|
301
|
-
# @param n Parameter mit dem TAKE_OR_DROP_CARROTS überprüft wird, default 0
|
302
|
-
# @return true, falls das Spielen der entsprechenden karte möglich ist
|
303
|
-
def self.is_valid_to_play_card(state, c, n = 0)
|
304
|
-
case c
|
305
|
-
when CardType::EAT_SALAD
|
306
|
-
isValidToPlayEatSalad(state)[0]
|
307
|
-
when CardType::FALL_BACK
|
308
|
-
is_valid_to_play_fall_back(state)[0]
|
309
|
-
when CardType::HURRY_AHEAD
|
310
|
-
is_valid_to_play_hurry_ahead(state)[0]
|
311
|
-
when CardType::TAKE_OR_DROP_CARROTS
|
312
|
-
is_valid_to_play_take_or_drop_carrots(state, n)[0]
|
313
|
-
else
|
314
|
-
raise "Unknown CardType " + c
|
315
|
-
end
|
316
|
-
end
|
317
|
-
|
318
|
-
def self.must_eat_salad(state)
|
319
|
-
player = state.current_player
|
320
|
-
# check whether player just moved to salad field and must eat salad
|
321
|
-
field = state.board.field(player.index)
|
322
|
-
if field.type == FieldType::SALAD
|
323
|
-
if player.last_non_skip_action&.instance_of?(Advance)
|
324
|
-
return true
|
325
|
-
elsif player.last_non_skip_action&.instance_of?(Card)
|
326
|
-
card_action = player.last_non_skip_action
|
327
|
-
if card_action.card_type == CardType::FALL_BACK ||
|
328
|
-
card_action.card_type == CardType::HURRY_AHEAD
|
329
|
-
return true
|
330
|
-
end
|
331
|
-
end
|
332
|
-
end
|
333
|
-
return false
|
334
|
-
end
|
335
|
-
|
336
|
-
# TODO difference isValidToPlayCard
|
337
|
-
# @param state
|
338
|
-
# @return
|
339
|
-
def self.can_play_card(state)
|
340
|
-
player = state.getCurrentPlayer()
|
341
|
-
canPlayCard = state.getTypeAt(player.getFieldIndex()).equals(FieldType.HARE)
|
342
|
-
player.getCards().each do |card|
|
343
|
-
canPlayCard = canPlayCard || is_valid_to_play_card(state, card, 0)
|
344
|
-
end
|
345
|
-
return canPlayCard
|
346
|
-
end
|
347
|
-
|
348
|
-
# TODO difference isVAlidTOMove
|
349
|
-
# @param state
|
350
|
-
# @return
|
351
|
-
def self.can_move(state)
|
352
|
-
can_move = false
|
353
|
-
max_distance = GameRules.calculate_movable_fields(state.getCurrentPlayer().getCarrots())
|
354
|
-
(1..max_distance).to_a.each do |i|
|
355
|
-
can_move = can_move || isValidToAdvance(state, i)
|
356
|
-
end
|
357
|
-
return can_move
|
358
|
-
end
|
359
|
-
|
360
|
-
# Überprüft ob eine Karte gespielt werden muss. Sollte nach einem
|
361
|
-
# Zug eines Spielers immer false sein, ansonsten ist Zug ungültig.
|
362
|
-
# @param state derzeitiger GameState
|
363
|
-
def self.must_play_card(state)
|
364
|
-
state.current_player.must_play_card
|
365
|
-
end
|
366
|
-
|
367
|
-
|
368
|
-
# Überprüft ob ein der derzeitige Spieler das Ziel betreten darf
|
369
|
-
# @param state GameState
|
370
|
-
# @return
|
371
|
-
def self.can_enter_goal(state)
|
372
|
-
player = state.current_player
|
373
|
-
player.carrots <= 10 && player.salads == 0
|
374
|
-
end
|
375
|
-
|
376
|
-
end
|