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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 2e078c2ea342feb8da789442c4ad6cdd3706ff15
4
- data.tar.gz: 90bcc5974f4a1388655101874062157a4f817dcd
3
+ metadata.gz: 4d55ebfd85d0ed449a11437fd91481c0b7b6f2e1
4
+ data.tar.gz: 027eadb37b996d8e07ad680db5aa97807d661051
5
5
  SHA512:
6
- metadata.gz: d673b541408a816e100fe286d370d661ac7991235049d610ecf855426c22f2548a425f0f77f82214db1c790e5b089b35bacdc5a3c083bbd85b91b0f2180d5182
7
- data.tar.gz: ff79a54d09798ce8222711ace8b15db2e8e09ece78d27b4f7278ee21e4b4274a735819ee55677ed8266881b3894f327f8f2134222c4f71f0211b63816117e0c7
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
- # This never includes external ruby files because of the
41
- # directory constraint at the top of this file:
42
- watch(/.*.rb/)
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
- mov = best_move
20
- logger.debug "Zug gefunden: #{mov}" unless mov.nil?
21
- mov
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
- # try all moves in all directions
27
- best = nil
28
- points_for_best = 0
29
- Direction.each do |direction|
30
- [1, 2].each do |speed|
31
- move = Move.new
32
- if gamestate.current_player.velocity != speed
33
- move.add_action(Acceleration.new(speed - gamestate.current_player.velocity))
34
- end
35
- # turn in that direction
36
- possible_turn = Direction.from_to(gamestate.current_player.direction, direction)
37
- if possible_turn.direction != 0
38
- move.add_action(possible_turn)
39
- end
40
- move.add_action(Advance.new(speed))
41
- gamestate_copy = gamestate.deep_clone
42
- begin
43
- logger.debug("Teste Zug #{move} auf gueltigkeit")
44
- move.perform!(gamestate_copy, gamestate_copy.current_player)
45
- points_for_move = gamestate_copy.points_for_player(gamestate_copy.current_player)
46
- logger.debug("Zug #{move} gueltig und wuerde #{points_for_move} Punkte geben!.")
47
- on_sandbank = gamestate_copy.board.field(gamestate_copy.current_player.x, gamestate_copy.current_player.y).type == FieldType::SANDBANK
48
- if !on_sandbank && (best.nil? || points_for_move > points_for_best)
49
- best = move
50
- points_for_best = points_for_move
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
- best
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/direction'
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, _current_player)
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
- # Accelerate by {#acceleration}. To decelerate, use a negative value.
32
- class Acceleration < Action
33
- attr_reader :acceleration
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(acceleration)
36
- @acceleration = acceleration
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 performed. Performing may change the game state.
42
- # @param current_player [Player] The player for which the action will be performed.
43
- def perform!(gamestate, current_player)
44
- new_velocity = current_player.velocity + acceleration
45
- if new_velocity < 1
46
- invalid 'Geschwindigkeit darf nicht unter 1 verringert werden.'
47
- end
48
- if new_velocity > 6
49
- invalid 'Geschwindigkeit darf nicht über 6 erhöht werden.'
50
- end
51
- acceleration.abs.times do
52
- if gamestate.free_acceleration?
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
- :acceleration
58
+ :advance
71
59
  end
72
60
 
73
61
  def ==(other)
74
- other.type == type && other.acceleration == acceleration
62
+ other.type == type && other.distance == distance
75
63
  end
76
64
  end
77
65
 
78
- # Turn by {#turn_steps}.
79
- class Turn < Action
80
- # Number of steps to turn. Negative values for turning clockwise, positive for
81
- # counterclockwise.
82
- attr_reader :turn_steps
83
-
84
- def initialize(turn_steps)
85
- @turn_steps = turn_steps
86
- end
87
-
88
- # (see Acceleration#perform!)
89
- def perform!(gamestate, current_player)
90
- invalid 'Drehung um 0 ist ungültig' if turn_steps.zero?
91
- if gamestate
92
- .board
93
- .field(current_player.x, current_player.y)
94
- .type == FieldType::SANDBANK
95
- invalid 'Drehung auf Sandbank nicht erlaubt'
96
- end
97
- needed_coal = turn_steps.abs
98
- needed_coal -= 1 if gamestate.free_turn?
99
- if needed_coal > 0 && gamestate.additional_free_turn_after_push?
100
- needed_coal -= 1
101
- gamestate.additional_free_turn_after_push = false
102
- end
103
- if needed_coal > current_player.coal
104
- invalid "Nicht genug Kohle für Drehung um #{turn_steps}. "\
105
- "Habe #{current_player.coal}, brauche #{needed_coal}."
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.direction =
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
- :turn
117
+ :card
116
118
  end
117
119
 
118
120
  def ==(other)
119
- other.type == type && other.turn_steps == turn_steps
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
- # Go forward in the current direction by {#distance}. When on a sandbank, a
124
- # value of -1 to go backwards is also legal.
125
- class Advance < Action
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
- # (see Acceleration#perform!)
133
- def perform!(gamestate, current_player)
134
- invalid 'Bewegung um 0 ist unzulässig.' if distance.zero?
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
- # returns the required movement points to perform this action
202
- def required_movement(gamestate, current_player)
203
- gamestate.board.get_all_in_direction(current_player.x, current_player.y, current_player.direction, distance).map do |field|
204
- # pushing costs one more movement
205
- on_opponent = field.x == gamestate.other_player.x && field.y == gamestate.other_player.y
206
- case field.type
207
- when FieldType::WATER, FieldType::GOAL, FieldType::SANDBANK
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
- :advance
150
+ :eat_salad
217
151
  end
218
152
 
219
153
  def ==(other)
220
- other.type == type && other.distance == distance
154
+ other.type == type
221
155
  end
222
156
  end
223
157
 
224
- # Push the opponent in {#direction}
225
- class Push < Action
226
- # @return [Direction] the direction where to push.
227
- attr_reader :direction
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
- # @param direction [Direction]
230
- def initialize(direction)
231
- @direction = direction
163
+ def initialize(value)
164
+ @value = value
232
165
  end
233
166
 
234
- # (see Acceleration#perform!)
235
- def perform!(gamestate, current_player)
236
- if gamestate.other_player.x != current_player.x ||
237
- gamestate.other_player.y != current_player.y
238
- invalid 'Abdrängen ist nur auf dem Feld des Gegners möglich.'
239
- end
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
- required_movement = 1
257
- if gamestate.board.field(target_x, target_y).type == FieldType::LOG
258
- required_movement += 1
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
- current_player.movement -= required_movement
178
+ def ==(other)
179
+ other.type == type && other.value == value
180
+ end
181
+ end
266
182
 
267
- gamestate.other_player.x = target_x
268
- gamestate.other_player.y = target_y
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
- :push
192
+ :fall_back
273
193
  end
274
194
 
275
195
  def ==(other)
276
- other.type == type && other.direction == direction
196
+ other.type == type
277
197
  end
278
198
  end