software_challenge_client 0.1.5 → 0.2.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/.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
|