software_challenge_client 1.2.1 → 19.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|