software_challenge_client 1.2.1 → 19.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. checksums.yaml +4 -4
  2. data/Dockerfile +3 -0
  3. data/README.md +64 -26
  4. data/example/client.rb +1 -71
  5. data/example/main.rb +1 -1
  6. data/lib/software_challenge_client.rb +4 -2
  7. data/lib/software_challenge_client/board.rb +66 -19
  8. data/lib/software_challenge_client/client_interface.rb +10 -5
  9. data/lib/software_challenge_client/condition.rb +2 -2
  10. data/lib/software_challenge_client/coordinates.rb +17 -0
  11. data/lib/software_challenge_client/debug_hint.rb +8 -4
  12. data/lib/software_challenge_client/direction.rb +53 -0
  13. data/lib/software_challenge_client/field.rb +26 -12
  14. data/lib/software_challenge_client/field_type.rb +25 -19
  15. data/lib/software_challenge_client/game_rule_logic.rb +230 -0
  16. data/lib/software_challenge_client/game_state.rb +45 -191
  17. data/lib/software_challenge_client/invalid_move_exception.rb +6 -8
  18. data/lib/software_challenge_client/line.rb +126 -0
  19. data/lib/software_challenge_client/line_direction.rb +15 -0
  20. data/lib/software_challenge_client/logging.rb +3 -2
  21. data/lib/software_challenge_client/move.rb +51 -38
  22. data/lib/software_challenge_client/network.rb +3 -1
  23. data/lib/software_challenge_client/player.rb +0 -39
  24. data/lib/software_challenge_client/player_color.rb +23 -13
  25. data/lib/software_challenge_client/protocol.rb +20 -83
  26. data/lib/software_challenge_client/runner.rb +2 -1
  27. data/lib/software_challenge_client/util/constants.rb +8 -5
  28. data/lib/software_challenge_client/version.rb +1 -1
  29. data/software_challenge_client.gemspec +2 -0
  30. metadata +24 -8
  31. data/lib/software_challenge_client/action.rb +0 -217
  32. data/lib/software_challenge_client/card_type.rb +0 -13
  33. data/lib/software_challenge_client/field_unavailable_exception.rb +0 -17
  34. data/lib/software_challenge_client/game_rules.rb +0 -376
@@ -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