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