software_challenge_client 0.1.5 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/.rspec +1 -0
- data/.rubocop.yml +6 -0
- data/AUTHORS +6 -0
- data/Guardfile +44 -0
- data/README.md +45 -0
- data/RELEASES.md +4 -0
- data/develop.sh +3 -0
- data/example/client.rb +45 -17
- data/example/main.rb +1 -1
- data/generate-authors.sh +19 -0
- data/lib/software_challenge_client.rb +18 -15
- data/lib/software_challenge_client/action.rb +278 -0
- data/lib/software_challenge_client/board.rb +74 -289
- data/lib/software_challenge_client/client_interface.rb +8 -3
- data/lib/software_challenge_client/condition.rb +2 -4
- data/lib/software_challenge_client/debug_hint.rb +3 -25
- data/lib/software_challenge_client/direction.rb +39 -0
- data/lib/software_challenge_client/field.rb +34 -12
- data/lib/software_challenge_client/field_type.rb +29 -8
- data/lib/software_challenge_client/field_unavailable_exception.rb +17 -0
- data/lib/software_challenge_client/game_state.rb +88 -255
- data/lib/software_challenge_client/invalid_move_exception.rb +16 -0
- data/lib/software_challenge_client/logging.rb +24 -0
- data/lib/software_challenge_client/move.rb +36 -35
- data/lib/software_challenge_client/network.rb +16 -19
- data/lib/software_challenge_client/player.rb +47 -10
- data/lib/software_challenge_client/player_color.rb +8 -7
- data/lib/software_challenge_client/protocol.rb +131 -83
- data/lib/software_challenge_client/runner.rb +9 -7
- data/lib/software_challenge_client/version.rb +1 -1
- data/release.sh +9 -0
- data/software_challenge_client.gemspec +20 -8
- metadata +175 -7
- data/lib/software_challenge_client/connection.rb +0 -56
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0f6e73a26e7e73031e6e8753b4530a3a97d7c6d4
|
4
|
+
data.tar.gz: 94b865f5a9919c92fdd383c7ee130b5a8444cc2a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2af181e53250e162c328d70512f12e98c01535f5cea29570dd787c34a2627575d73bd8ee832e4b72ffd33e390d15b064014b0f6d6019269cccd73c4d21d50a9a
|
7
|
+
data.tar.gz: 57434f1b54bd0723e8d1af763780cc3ad25cb79ec0476eeb924d1b812d4862e05451a1b88baf98a9816860e0c4b8c9d18c0b7ffb348e129a93f917aac8d32922
|
data/.gitignore
CHANGED
data/.rspec
CHANGED
data/.rubocop.yml
ADDED
data/AUTHORS
ADDED
data/Guardfile
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
# A sample Guardfile
|
3
|
+
# More info at https://github.com/guard/guard#readme
|
4
|
+
|
5
|
+
## Uncomment and set this to only include directories you want to watch
|
6
|
+
directories %w(lib lib/software_challenge_client spec)
|
7
|
+
# the following seems to cause problems in certain ruby versions:
|
8
|
+
# directories %w(lib lib/software_challenge_client spec).select do |d|
|
9
|
+
# Dir.exist?(d) ? d : UI.warning("Directory #{d} does not exist")
|
10
|
+
# end
|
11
|
+
|
12
|
+
## Note: if you are using the `directories` clause above and you are not
|
13
|
+
## watching the project directory ('.'), then you will want to move
|
14
|
+
## the Guardfile to a watched dir and symlink it back, e.g.
|
15
|
+
#
|
16
|
+
# $ mkdir config
|
17
|
+
# $ mv Guardfile config/
|
18
|
+
# $ ln -s config/Guardfile .
|
19
|
+
#
|
20
|
+
# and, you'll have to watch "config/Guardfile" instead of "Guardfile"
|
21
|
+
|
22
|
+
# This group allows to skip running RuboCop when RSpec failed.
|
23
|
+
group :red_green_refactor, halt_on_fail: true do
|
24
|
+
guard :rspec, cmd: 'rspec', all_after_pass: true, all_on_start: true do
|
25
|
+
watch(%r{^lib/software_challenge_client/(.+)\.rb$}) do |match|
|
26
|
+
spec_file = "spec/#{match[1]}_spec.rb"
|
27
|
+
if File.exist?(spec_file)
|
28
|
+
spec_file # run spec belonging to the file which was changed
|
29
|
+
else
|
30
|
+
'spec' # run all specs
|
31
|
+
end
|
32
|
+
end
|
33
|
+
watch(%r{^spec/.+_spec\.rb$}) # no block means the matched file is returned
|
34
|
+
|
35
|
+
watch('lib/software_challenge_client.rb') { 'spec' }
|
36
|
+
watch('spec/spec_helper.rb') { 'spec' }
|
37
|
+
end
|
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
|
44
|
+
end
|
data/README.md
CHANGED
@@ -33,6 +33,37 @@ in a shell (while being in the example directory). Note that the
|
|
33
33
|
software_challenge_client gem needs to be installed for this to work and a
|
34
34
|
server waiting for a manual client has to be running.
|
35
35
|
|
36
|
+
## Documentation
|
37
|
+
|
38
|
+
Code documentation can be generated using YARD in the project root (source code
|
39
|
+
needs to be checked out and `bundle` has to be executed,
|
40
|
+
see [Installation](#installation)):
|
41
|
+
|
42
|
+
```console
|
43
|
+
yard
|
44
|
+
```
|
45
|
+
|
46
|
+
After generation, the docs can be found in the `doc` directory. Start at
|
47
|
+
`index.html`.
|
48
|
+
|
49
|
+
Documentation for the latest source can also be found
|
50
|
+
on
|
51
|
+
[rubydoc.info](http://www.rubydoc.info/github/CAU-Kiel-Tech-Inf/socha_ruby_client).
|
52
|
+
|
53
|
+
When updating the docs, you may use
|
54
|
+
|
55
|
+
```console
|
56
|
+
yard server --reload
|
57
|
+
```
|
58
|
+
|
59
|
+
or inside a docker container
|
60
|
+
|
61
|
+
```console
|
62
|
+
yard server --reload --bind 0.0.0.0
|
63
|
+
```
|
64
|
+
|
65
|
+
to get a live preview of them at [http://localhost:8808](http://localhost:8808).
|
66
|
+
|
36
67
|
## Development
|
37
68
|
|
38
69
|
After checking out the repo, run `bin/setup` to install
|
@@ -43,6 +74,20 @@ experiment.
|
|
43
74
|
To install this gem onto your local machine, run `bundle exec rake
|
44
75
|
install`.
|
45
76
|
|
77
|
+
To develop inside a docker container, use the included `Dockerfile` and
|
78
|
+
`develop.sh`.
|
79
|
+
|
80
|
+
### Specs
|
81
|
+
|
82
|
+
The gem is tested using RSpec. To run all tests, execute `rspec`. When
|
83
|
+
developing, you may use Guard to execute tests when files change. To do this,
|
84
|
+
execute `guard`. Tests will then be automatically run when you change a file.
|
85
|
+
|
86
|
+
### Linting
|
87
|
+
|
88
|
+
Linting by rubocop is included in the guard config. It is run when all specs
|
89
|
+
pass.
|
90
|
+
|
46
91
|
### Releasing
|
47
92
|
|
48
93
|
To release a new version, update the version number in
|
data/RELEASES.md
CHANGED
data/develop.sh
ADDED
data/example/client.rb
CHANGED
@@ -1,31 +1,59 @@
|
|
1
1
|
# encoding: UTF-8
|
2
2
|
require 'software_challenge_client'
|
3
3
|
|
4
|
+
# This is an example of a client playing the game using the software challenge
|
5
|
+
# gem.
|
4
6
|
class Client < ClientInterface
|
7
|
+
include Logging
|
8
|
+
|
5
9
|
attr_accessor :gamestate
|
6
10
|
|
7
|
-
def initialize
|
8
|
-
|
11
|
+
def initialize(log_level)
|
12
|
+
logger.level = log_level
|
13
|
+
logger.info 'Einfacher Spieler wurde erstellt.'
|
9
14
|
end
|
10
15
|
|
11
16
|
# gets called, when it's your turn
|
12
|
-
def
|
13
|
-
|
14
|
-
mov =
|
15
|
-
unless mov.nil?
|
16
|
-
|
17
|
-
puts mov.to_s
|
18
|
-
end
|
19
|
-
return mov
|
17
|
+
def move_requested
|
18
|
+
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
|
20
22
|
end
|
21
23
|
|
22
|
-
# choose a
|
23
|
-
def
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
24
|
+
# choose a move giving the most points
|
25
|
+
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
|
51
|
+
end
|
52
|
+
rescue InvalidMoveException => e
|
53
|
+
logger.debug("Zug #{move} ist ungueltig: #{e}")
|
54
|
+
end
|
55
|
+
end
|
29
56
|
end
|
57
|
+
best
|
30
58
|
end
|
31
59
|
end
|
data/example/main.rb
CHANGED
data/generate-authors.sh
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
#!/bin/bash
|
2
|
+
# Thanks to the docker project!
|
3
|
+
# https://github.com/docker/docker/blob/2c224e4fc09518d33780d818cf74026f6aa32744/hack/generate-authors.sh
|
4
|
+
set -e
|
5
|
+
|
6
|
+
# change to the directory where the script is located, this should be the project root
|
7
|
+
pushd "$(dirname "$(readlink -f "$BASH_SOURCE")")/"
|
8
|
+
|
9
|
+
# see also ".mailmap" for how email addresses and names are deduplicated
|
10
|
+
|
11
|
+
{
|
12
|
+
cat <<-'EOH'
|
13
|
+
# This file lists all individuals having contributed content to the repository.
|
14
|
+
# For how it is generated, see `generate-authors.sh`.
|
15
|
+
EOH
|
16
|
+
echo
|
17
|
+
git log --format='%aN <%aE>' | LC_ALL=C.UTF-8 sort -uf
|
18
|
+
} > AUTHORS
|
19
|
+
popd
|
@@ -1,18 +1,21 @@
|
|
1
1
|
# encoding: UTF-8
|
2
2
|
module SoftwareChallengeClient
|
3
|
-
require
|
4
|
-
require
|
5
|
-
require
|
6
|
-
require
|
7
|
-
require
|
8
|
-
require
|
9
|
-
require
|
10
|
-
require
|
11
|
-
require
|
12
|
-
require
|
13
|
-
require
|
14
|
-
require
|
15
|
-
require
|
16
|
-
require
|
17
|
-
require
|
3
|
+
require 'software_challenge_client/version'
|
4
|
+
require 'software_challenge_client/logging'
|
5
|
+
require 'software_challenge_client/invalid_move_exception'
|
6
|
+
require 'software_challenge_client/field_unavailable_exception'
|
7
|
+
require 'software_challenge_client/board'
|
8
|
+
require 'software_challenge_client/client_interface'
|
9
|
+
require 'software_challenge_client/condition'
|
10
|
+
require 'software_challenge_client/debug_hint'
|
11
|
+
require 'software_challenge_client/field'
|
12
|
+
require 'software_challenge_client/field_type'
|
13
|
+
require 'software_challenge_client/game_state'
|
14
|
+
require 'software_challenge_client/move'
|
15
|
+
require 'software_challenge_client/network'
|
16
|
+
require 'software_challenge_client/player'
|
17
|
+
require 'software_challenge_client/player_color'
|
18
|
+
require 'software_challenge_client/protocol'
|
19
|
+
require 'software_challenge_client/runner'
|
20
|
+
require 'software_challenge_client/direction'
|
18
21
|
end
|
@@ -0,0 +1,278 @@
|
|
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, _current_player)
|
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
|
+
# Accelerate by {#acceleration}. To decelerate, use a negative value.
|
32
|
+
class Acceleration < Action
|
33
|
+
attr_reader :acceleration
|
34
|
+
|
35
|
+
def initialize(acceleration)
|
36
|
+
@acceleration = acceleration
|
37
|
+
end
|
38
|
+
|
39
|
+
# Perform the action.
|
40
|
+
#
|
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.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
|
59
|
+
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
|
+
end
|
68
|
+
|
69
|
+
def type
|
70
|
+
:acceleration
|
71
|
+
end
|
72
|
+
|
73
|
+
def ==(other)
|
74
|
+
other.type == type && other.acceleration == acceleration
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
# Turn by {#direction}.
|
79
|
+
class Turn < Action
|
80
|
+
# Number of steps to turn. Negative values for turning clockwise, positive for
|
81
|
+
# counterclockwise.
|
82
|
+
attr_reader :direction
|
83
|
+
|
84
|
+
def initialize(direction)
|
85
|
+
@direction = direction
|
86
|
+
end
|
87
|
+
|
88
|
+
# (see Acceleration#perform!)
|
89
|
+
def perform!(gamestate, current_player)
|
90
|
+
invalid 'Drehung um 0 ist ungültig' if direction.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 = direction.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 #{direction}. "\
|
105
|
+
"Habe #{current_player.coal}, brauche #{needed_coal}."
|
106
|
+
end
|
107
|
+
|
108
|
+
current_player.direction =
|
109
|
+
Direction.get_turn_direction(current_player.direction, direction)
|
110
|
+
current_player.coal -= [0, needed_coal].max
|
111
|
+
gamestate.free_turn = false
|
112
|
+
end
|
113
|
+
|
114
|
+
def type
|
115
|
+
:turn
|
116
|
+
end
|
117
|
+
|
118
|
+
def ==(other)
|
119
|
+
other.type == type && other.direction == direction
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
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
|
130
|
+
end
|
131
|
+
|
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
|
198
|
+
|
199
|
+
end
|
200
|
+
|
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(:+)
|
213
|
+
end
|
214
|
+
|
215
|
+
def type
|
216
|
+
:advance
|
217
|
+
end
|
218
|
+
|
219
|
+
def ==(other)
|
220
|
+
other.type == type && other.distance == distance
|
221
|
+
end
|
222
|
+
end
|
223
|
+
|
224
|
+
# Push the opponent in {#direction}
|
225
|
+
class Push < Action
|
226
|
+
# @return [Direction] the direction where to push.
|
227
|
+
attr_reader :direction
|
228
|
+
|
229
|
+
# @param direction [Direction]
|
230
|
+
def initialize(direction)
|
231
|
+
@direction = direction
|
232
|
+
end
|
233
|
+
|
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
|
+
)
|
255
|
+
|
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
|
264
|
+
|
265
|
+
current_player.movement -= required_movement
|
266
|
+
|
267
|
+
gamestate.other_player.x = target_x
|
268
|
+
gamestate.other_player.y = target_y
|
269
|
+
end
|
270
|
+
|
271
|
+
def type
|
272
|
+
:push
|
273
|
+
end
|
274
|
+
|
275
|
+
def ==(other)
|
276
|
+
other.type == type && other.direction == direction
|
277
|
+
end
|
278
|
+
end
|