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.
Files changed (34) hide show
  1. checksums.yaml +4 -4
  2. data/Dockerfile +3 -0
  3. data/README.md +64 -26
  4. data/example/client.rb +1 -71
  5. data/example/main.rb +1 -1
  6. data/lib/software_challenge_client.rb +4 -2
  7. data/lib/software_challenge_client/board.rb +66 -19
  8. data/lib/software_challenge_client/client_interface.rb +10 -5
  9. data/lib/software_challenge_client/condition.rb +2 -2
  10. data/lib/software_challenge_client/coordinates.rb +17 -0
  11. data/lib/software_challenge_client/debug_hint.rb +8 -4
  12. data/lib/software_challenge_client/direction.rb +53 -0
  13. data/lib/software_challenge_client/field.rb +26 -12
  14. data/lib/software_challenge_client/field_type.rb +25 -19
  15. data/lib/software_challenge_client/game_rule_logic.rb +230 -0
  16. data/lib/software_challenge_client/game_state.rb +45 -191
  17. data/lib/software_challenge_client/invalid_move_exception.rb +6 -8
  18. data/lib/software_challenge_client/line.rb +126 -0
  19. data/lib/software_challenge_client/line_direction.rb +15 -0
  20. data/lib/software_challenge_client/logging.rb +3 -2
  21. data/lib/software_challenge_client/move.rb +51 -38
  22. data/lib/software_challenge_client/network.rb +3 -1
  23. data/lib/software_challenge_client/player.rb +0 -39
  24. data/lib/software_challenge_client/player_color.rb +23 -13
  25. data/lib/software_challenge_client/protocol.rb +20 -83
  26. data/lib/software_challenge_client/runner.rb +2 -1
  27. data/lib/software_challenge_client/util/constants.rb +8 -5
  28. data/lib/software_challenge_client/version.rb +1 -1
  29. data/software_challenge_client.gemspec +2 -0
  30. metadata +24 -8
  31. data/lib/software_challenge_client/action.rb +0 -217
  32. data/lib/software_challenge_client/card_type.rb +0 -13
  33. data/lib/software_challenge_client/field_unavailable_exception.rb +0 -17
  34. data/lib/software_challenge_client/game_rules.rb +0 -376
@@ -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 2018'
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: UTF-8
2
- #Game constants
1
+ # encoding: utf-8
2
+ # frozen_string_literal: true
3
+
4
+ # Konstanten zum aktuellen Spiel.
3
5
  module Constants
4
- ROUND_LIMIT = 100
5
- SIZE = 24 #board's width and height
6
- end
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
@@ -1,4 +1,4 @@
1
1
  # encoding: UTF-8
2
2
  module SoftwareChallengeClient
3
- VERSION = "1.2.1"
3
+ VERSION = "19.0.0"
4
4
  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: 1.2.1
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: 2017-11-22 00:00:00.000000000 Z
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/field_unavailable_exception.rb
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: '0'
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.13
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