software_challenge_client 0.3.4 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.rubocop.yml +3 -1
- data/.ruby-version +1 -0
- data/Guardfile +5 -5
- data/RELEASES.md +6 -0
- data/example/client.rb +71 -33
- data/lib/software_challenge_client.rb +1 -1
- data/lib/software_challenge_client/action.rb +116 -196
- data/lib/software_challenge_client/board.rb +17 -73
- data/lib/software_challenge_client/card_type.rb +13 -0
- data/lib/software_challenge_client/field.rb +10 -44
- data/lib/software_challenge_client/field_type.rb +20 -26
- data/lib/software_challenge_client/game_rules.rb +378 -0
- data/lib/software_challenge_client/game_state.rb +167 -49
- data/lib/software_challenge_client/move.rb +23 -13
- data/lib/software_challenge_client/player.rb +35 -34
- data/lib/software_challenge_client/player_color.rb +1 -1
- data/lib/software_challenge_client/protocol.rb +82 -39
- data/lib/software_challenge_client/version.rb +1 -1
- metadata +6 -5
- data/lib/software_challenge_client/direction.rb +0 -39
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4d55ebfd85d0ed449a11437fd91481c0b7b6f2e1
|
4
|
+
data.tar.gz: 027eadb37b996d8e07ad680db5aa97807d661051
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c78a79ba1d86132c4abb89c489a49480b2a71d14449edc83e11d2eba20ea3bc5bdce3deaddc2d685ff63673631df65c6efd9f4ffdbb3f7638a5dc2ee12736990
|
7
|
+
data.tar.gz: b70311d5c10252ce0a92c78a3e0f1623d055a93d9b1a6b9bdd39fbc2b0d60a29c7c39261bd54edf41af2707b7838884a5b02331c53e23a18eff42fbf4713b248
|
data/.rubocop.yml
CHANGED
@@ -1,6 +1,8 @@
|
|
1
1
|
AllCops:
|
2
|
+
TargetRubyVersion: 2.4
|
2
3
|
DefaultFormatter: fuubar
|
3
4
|
DisplayStyleGuide: true
|
4
5
|
DisplayCopNames: false
|
5
|
-
TargetRubyVersion: 2.0
|
6
6
|
#require: rubocop-rspec
|
7
|
+
Style/AsciiComments:
|
8
|
+
Enabled: false # we use german comments to document relevant methods for the pupils
|
data/.ruby-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
2.4.1
|
data/Guardfile
CHANGED
@@ -36,9 +36,9 @@ group :red_green_refactor, halt_on_fail: true do
|
|
36
36
|
watch('spec/spec_helper.rb') { 'spec' }
|
37
37
|
end
|
38
38
|
|
39
|
-
guard :rubocop, all_on_start: false do
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
end
|
39
|
+
# guard :rubocop, all_on_start: false do
|
40
|
+
# # This never includes external ruby files because of the
|
41
|
+
# # directory constraint at the top of this file:
|
42
|
+
# watch(/.*.rb/)
|
43
|
+
# end
|
44
44
|
end
|
data/RELEASES.md
CHANGED
@@ -1,5 +1,11 @@
|
|
1
|
+
= 1.0.0
|
2
|
+
|
3
|
+
First version for game "Hase und Igel".
|
4
|
+
|
1
5
|
= 0.3.4
|
2
6
|
|
7
|
+
Last version for game "Mississippi Queen".
|
8
|
+
|
3
9
|
- Renamed Turn#direction to Turn#turn_steps to make clearer that a number should be given, not a Direction instance.
|
4
10
|
- Corrected generation of XML for Push-actions
|
5
11
|
|
data/example/client.rb
CHANGED
@@ -8,6 +8,9 @@ class Client < ClientInterface
|
|
8
8
|
|
9
9
|
attr_accessor :gamestate
|
10
10
|
|
11
|
+
# Anzahl der Spielfelder
|
12
|
+
NUM_FIELDS = 65
|
13
|
+
|
11
14
|
def initialize(log_level)
|
12
15
|
logger.level = log_level
|
13
16
|
logger.info 'Einfacher Spieler wurde erstellt.'
|
@@ -16,44 +19,79 @@ class Client < ClientInterface
|
|
16
19
|
# gets called, when it's your turn
|
17
20
|
def move_requested
|
18
21
|
logger.info "Spielstand: #{gamestate.points_for_player(gamestate.current_player)} - #{gamestate.points_for_player(gamestate.other_player)}"
|
19
|
-
|
20
|
-
logger.debug "Zug gefunden: #{
|
21
|
-
|
22
|
+
move = best_move
|
23
|
+
logger.debug "Zug gefunden: #{move}" unless move.nil?
|
24
|
+
move
|
22
25
|
end
|
23
26
|
|
24
|
-
# choose a move giving the most points
|
25
27
|
def best_move
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
28
|
+
possible_moves = gamestate.possible_moves # Enthält mindestens ein Element
|
29
|
+
salad_moves = []
|
30
|
+
winning_moves = []
|
31
|
+
selected_moves = []
|
32
|
+
|
33
|
+
index = gamestate.current_player.index
|
34
|
+
possible_moves.each do |move|
|
35
|
+
move.actions.each do |action|
|
36
|
+
case action.type
|
37
|
+
when :advance
|
38
|
+
target_field_index = action.distance + index
|
39
|
+
if target_field_index == NUM_FIELDS - 1
|
40
|
+
winning_moves << move
|
41
|
+
elsif gamestate.field(target_field_index).type == FieldType::SALAD
|
42
|
+
salad_moves << move
|
43
|
+
else
|
44
|
+
selected_moves << move
|
45
|
+
end
|
46
|
+
when :card
|
47
|
+
if action.card_type == CardType::EAT_SALAD
|
48
|
+
# Zug auf Hasenfeld und danach Salatkarte
|
49
|
+
salad_moves << move
|
50
|
+
# Muss nicht zusätzlich ausgewählt werden, wurde schon durch Advance ausgewählt
|
51
|
+
end
|
52
|
+
when :exchange_carrots
|
53
|
+
if action.value == 10 &&
|
54
|
+
gamestate.current_player.carrots < 30 &&
|
55
|
+
index < 40 &&
|
56
|
+
!gamestate.current_player.last_non_skip_action.instance_of?(ExchangeCarrots)
|
57
|
+
# Nehme nur Karotten auf, wenn weniger als 30 und nur am Anfang und nicht zwei
|
58
|
+
# mal hintereinander
|
59
|
+
selected_moves << move
|
60
|
+
elsif action.value == -10 &&
|
61
|
+
gamestate.current_player.carrots > 30 &&
|
62
|
+
index >= 40
|
63
|
+
# Abgeben von Karotten ist nur am Ende sinnvoll
|
64
|
+
selected_moves << move
|
65
|
+
end
|
66
|
+
when :fall_back
|
67
|
+
if index > 56 && # letztes Salatfeld
|
68
|
+
gamestate.current_player.salads > 0
|
69
|
+
# Falle nur am Ende (index > 56) zurück, außer du musst noch einen Salat loswerden
|
70
|
+
selected_moves << move
|
71
|
+
elsif index <= 56 &&
|
72
|
+
gamestate.previous_field_of_type(FieldType::HEDGEHOG, index) &&
|
73
|
+
index - gamestate.previous_field_of_type(FieldType::HEDGEHOG, index).index < 5
|
74
|
+
# Falle zurück, falls sich Rückzug lohnt (nicht zu viele Karotten aufnehmen)
|
75
|
+
selected_moves << move
|
76
|
+
end
|
77
|
+
else
|
78
|
+
# Füge Salatessen oder Skip hinzu
|
79
|
+
selected_moves << move
|
51
80
|
end
|
52
|
-
rescue InvalidMoveException => e
|
53
|
-
logger.debug("Zug #{move} ist ungueltig: #{e}")
|
54
|
-
end
|
55
81
|
end
|
56
82
|
end
|
57
|
-
|
83
|
+
|
84
|
+
if !winning_moves.empty?
|
85
|
+
logger.info("Waehle Gewinnzug")
|
86
|
+
winning_moves.sample
|
87
|
+
elsif !salad_moves.empty?
|
88
|
+
# es gibt die Möglichkeit einen Salat zu essen
|
89
|
+
logger.info("Waehle Zug zum Salatessen")
|
90
|
+
salad_moves.sample
|
91
|
+
elsif !selected_moves.empty?
|
92
|
+
selected_moves.sample
|
93
|
+
else
|
94
|
+
possible_moves.sample
|
95
|
+
end
|
58
96
|
end
|
59
97
|
end
|
@@ -17,5 +17,5 @@ module SoftwareChallengeClient
|
|
17
17
|
require 'software_challenge_client/player_color'
|
18
18
|
require 'software_challenge_client/protocol'
|
19
19
|
require 'software_challenge_client/runner'
|
20
|
-
require 'software_challenge_client/
|
20
|
+
require 'software_challenge_client/game_rules'
|
21
21
|
end
|
@@ -13,7 +13,7 @@ class Action
|
|
13
13
|
raise 'must be overridden'
|
14
14
|
end
|
15
15
|
|
16
|
-
def perform!(_gamestate
|
16
|
+
def perform!(_gamestate)
|
17
17
|
raise 'must be overridden'
|
18
18
|
end
|
19
19
|
|
@@ -28,251 +28,171 @@ class Action
|
|
28
28
|
end
|
29
29
|
end
|
30
30
|
|
31
|
-
#
|
32
|
-
|
33
|
-
|
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
|
34
35
|
|
35
|
-
def initialize(
|
36
|
-
@
|
36
|
+
def initialize(distance)
|
37
|
+
@distance = distance
|
37
38
|
end
|
38
39
|
|
39
40
|
# Perform the action.
|
40
41
|
#
|
41
|
-
# @param gamestate [GameState] The game state on which the action will be
|
42
|
-
#
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
gamestate.free_acceleration = false
|
54
|
-
elsif current_player.coal.zero?
|
55
|
-
invalid 'Nicht genug Kohle zum Beschleunigen.'
|
56
|
-
else
|
57
|
-
current_player.coal -= 1
|
58
|
-
end
|
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
|
59
54
|
end
|
60
|
-
if gamestate.board.field(current_player.x, current_player.y).type == FieldType::SANDBANK
|
61
|
-
invalid 'Auf einer Sandbank kann nicht beschleunigt werden.'
|
62
|
-
end
|
63
|
-
current_player.velocity = new_velocity
|
64
|
-
# This works only when acceleration is the first action in a move. The move
|
65
|
-
# class has to check that.
|
66
|
-
current_player.movement = new_velocity
|
67
55
|
end
|
68
56
|
|
69
57
|
def type
|
70
|
-
:
|
58
|
+
:advance
|
71
59
|
end
|
72
60
|
|
73
61
|
def ==(other)
|
74
|
-
other.type == type && other.
|
62
|
+
other.type == type && other.distance == distance
|
75
63
|
end
|
76
64
|
end
|
77
65
|
|
78
|
-
#
|
79
|
-
class
|
80
|
-
#
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
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}!"
|
106
111
|
end
|
107
|
-
|
108
|
-
current_player.
|
109
|
-
Direction.get_turn_direction(current_player.direction, turn_steps)
|
110
|
-
current_player.coal -= [0, needed_coal].max
|
111
|
-
gamestate.free_turn = false
|
112
|
+
gamestate.set_last_action(self)
|
113
|
+
gamestate.current_player.cards.delete(self.type)
|
112
114
|
end
|
113
115
|
|
114
116
|
def type
|
115
|
-
:
|
117
|
+
:card
|
116
118
|
end
|
117
119
|
|
118
120
|
def ==(other)
|
119
|
-
other.
|
121
|
+
other.card_type == card_type &&
|
122
|
+
(card_type != CardType::TAKE_OR_DROP_CARROTS || (other.value == value))
|
120
123
|
end
|
121
124
|
end
|
122
125
|
|
123
|
-
#
|
124
|
-
|
125
|
-
|
126
|
-
attr_reader :distance
|
127
|
-
|
128
|
-
def initialize(distance)
|
129
|
-
@distance = distance
|
126
|
+
# Ein Aussetzzug. Ist nur erlaubt, sollten keine anderen Züge möglich sei
|
127
|
+
class Skip < Action
|
128
|
+
def initialize()
|
130
129
|
end
|
131
130
|
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
if distance < 0 && gamestate.board.field(current_player.x, current_player.y).type != FieldType::SANDBANK
|
136
|
-
invalid 'Negative Bewegung ist nur auf Sandbank erlaubt.'
|
137
|
-
end
|
138
|
-
begin
|
139
|
-
fields = gamestate.board.get_all_in_direction(
|
140
|
-
current_player.x, current_player.y, current_player.direction, distance
|
141
|
-
)
|
142
|
-
rescue FieldUnavailableException => e
|
143
|
-
invalid "Feld (#{e.x}, #{e.y}) ist nicht vorhanden"
|
144
|
-
end
|
145
|
-
# test if all fields are passable
|
146
|
-
if fields.any?(&:blocked?)
|
147
|
-
invalid 'Der Weg ist blockiert.'
|
148
|
-
end
|
149
|
-
# Test if movement is enough.
|
150
|
-
req_movement = required_movement(gamestate, current_player)
|
151
|
-
if req_movement > current_player.movement
|
152
|
-
invalid 'Nicht genug Bewegungspunkte.'
|
153
|
-
end
|
154
|
-
# test if opponent is not on fields over which is moved
|
155
|
-
if fields[0...-1].any? { |f| gamestate.occupied_by_other_player? f }
|
156
|
-
invalid 'Man darf nicht über den Gegner fahren.'
|
157
|
-
end
|
158
|
-
# test if moving over sandbank
|
159
|
-
if fields[0...-1].any? { |f| f.type == FieldType::SANDBANK }
|
160
|
-
invalid 'Die Bewegung darf nur auf einer Sandbank enden, '\
|
161
|
-
'nicht über sie hinaus gehen.'
|
162
|
-
end
|
163
|
-
target_field = fields.last
|
164
|
-
current_player.x = target_field.x
|
165
|
-
current_player.y = target_field.y
|
166
|
-
|
167
|
-
if target_field.type == FieldType::SANDBANK
|
168
|
-
current_player.movement = 0
|
169
|
-
current_player.velocity = 1
|
170
|
-
else
|
171
|
-
current_player.movement -= req_movement
|
172
|
-
end
|
173
|
-
|
174
|
-
# test for passenger
|
175
|
-
if current_player.velocity == 1
|
176
|
-
required_field_for_direction = {
|
177
|
-
Direction::RIGHT.key=> FieldType::PASSENGER3.key,
|
178
|
-
Direction::UP_RIGHT.key=> FieldType::PASSENGER4.key,
|
179
|
-
Direction::UP_LEFT.key=> FieldType::PASSENGER5.key,
|
180
|
-
Direction::LEFT.key=> FieldType::PASSENGER0.key,
|
181
|
-
Direction::DOWN_LEFT.key=> FieldType::PASSENGER2.key,
|
182
|
-
Direction::DOWN_RIGHT.key=> FieldType::PASSENGER1.key
|
183
|
-
}
|
184
|
-
Direction.each do |direction|
|
185
|
-
begin
|
186
|
-
neighbor = gamestate.board.get_in_direction(current_player.x, current_player.y, direction)
|
187
|
-
if neighbor.type.key == required_field_for_direction[direction.key]
|
188
|
-
if current_player.passengers < 2
|
189
|
-
current_player.passengers += 1
|
190
|
-
neighbor.type = FieldType::BLOCKED
|
191
|
-
end
|
192
|
-
end
|
193
|
-
rescue FieldUnavailableException
|
194
|
-
# neighbor did not exist, that is okay
|
195
|
-
end
|
196
|
-
end
|
197
|
-
end
|
131
|
+
def type
|
132
|
+
:skip
|
133
|
+
end
|
198
134
|
|
135
|
+
def ==(other)
|
136
|
+
other.type == type
|
199
137
|
end
|
138
|
+
end
|
200
139
|
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
on_opponent ? 2 : 1
|
209
|
-
when FieldType::LOG
|
210
|
-
on_opponent ? 3 : 2
|
211
|
-
end
|
212
|
-
end.reduce(:+)
|
140
|
+
# Eine Salatessen-Aktion. Kann nur auf einem Salatfeld ausgeführt werden. Muss ausgeführt werden,
|
141
|
+
# ein Salatfeld betreten wird. Nachdem die Aktion ausgefürht wurde, muss das Salatfeld verlassen
|
142
|
+
# werden, oder es muss ausgesetzt werden.
|
143
|
+
# Duch eine Salatessen-Aktion wird ein Salat verbraucht und es werden je nachdem ob der Spieler führt
|
144
|
+
# oder nicht 10 oder 30 Karotten aufgenommen.
|
145
|
+
class EatSalad < Action
|
146
|
+
def initialize()
|
213
147
|
end
|
214
148
|
|
215
149
|
def type
|
216
|
-
:
|
150
|
+
:eat_salad
|
217
151
|
end
|
218
152
|
|
219
153
|
def ==(other)
|
220
|
-
other.type == type
|
154
|
+
other.type == type
|
221
155
|
end
|
222
156
|
end
|
223
157
|
|
224
|
-
#
|
225
|
-
|
226
|
-
|
227
|
-
|
158
|
+
# Karottentauschaktion. Es können auf einem Karottenfeld 10 Karotten abgegeben oder aufgenommen werden.
|
159
|
+
# Dies kann beliebig oft hintereinander ausgeführt werden.
|
160
|
+
class ExchangeCarrots < Action
|
161
|
+
attr_accessor :value
|
228
162
|
|
229
|
-
|
230
|
-
|
231
|
-
@direction = direction
|
163
|
+
def initialize(value)
|
164
|
+
@value = value
|
232
165
|
end
|
233
166
|
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
other_player_field =
|
241
|
-
gamestate.board.field(gamestate.other_player.x, gamestate.other_player.y)
|
242
|
-
if other_player_field.type == FieldType::SANDBANK
|
243
|
-
invalid 'Abdrängen von einer Sandbank ist nicht erlaubt.'
|
244
|
-
end
|
245
|
-
if direction == Direction.get_turn_direction(current_player.direction, 3)
|
246
|
-
invalid 'Man darf nicht hinter sich abdrängen.'
|
247
|
-
end
|
248
|
-
|
249
|
-
target_x, target_y =
|
250
|
-
gamestate.board.get_neighbor(
|
251
|
-
gamestate.other_player.x,
|
252
|
-
gamestate.other_player.y,
|
253
|
-
direction
|
254
|
-
)
|
167
|
+
def perform!(gamestate)
|
168
|
+
valid, message = GameRules.is_valid_to_exchange_carrots(gamestate, value)
|
169
|
+
raise InvalidMoveException("Es können nicht #{value} Karotten aufgenommen werden. " + message) unless valid
|
170
|
+
gamestate.current_player.carrots += value
|
171
|
+
gamestate.set_last_action(self)
|
172
|
+
end
|
255
173
|
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
end
|
260
|
-
if required_movement > current_player.movement
|
261
|
-
invalid 'Nicht genug Bewegungspunkte zum abdrängen '\
|
262
|
-
"(brauche #{required_movement})"
|
263
|
-
end
|
174
|
+
def type
|
175
|
+
:exchange_carrots
|
176
|
+
end
|
264
177
|
|
265
|
-
|
178
|
+
def ==(other)
|
179
|
+
other.type == type && other.value == value
|
180
|
+
end
|
181
|
+
end
|
266
182
|
|
267
|
-
|
268
|
-
|
183
|
+
class FallBack < Action
|
184
|
+
def perform!(gamestate)
|
185
|
+
valid, message = GameRules.is_valid_to_fall_back(gamestate)
|
186
|
+
raise InvalidMoveException("Es kann gerade kein Rückzug gemacht werden. " + message) unless valid
|
187
|
+
gamestate.current_player.index = gamestate.previous_field_of_type(FieldType::HEDGEHOG, gamestate.current_player.index).index
|
188
|
+
gamestate.set_last_action(self)
|
269
189
|
end
|
270
190
|
|
271
191
|
def type
|
272
|
-
:
|
192
|
+
:fall_back
|
273
193
|
end
|
274
194
|
|
275
195
|
def ==(other)
|
276
|
-
other.type == type
|
196
|
+
other.type == type
|
277
197
|
end
|
278
198
|
end
|