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
@@ -1,101 +1,45 @@
|
|
1
|
-
# encoding:
|
1
|
+
# encoding: utf-8
|
2
2
|
require_relative './util/constants'
|
3
3
|
require_relative 'game_state'
|
4
4
|
require_relative 'player'
|
5
5
|
require_relative 'field_type'
|
6
6
|
require_relative 'field'
|
7
7
|
|
8
|
-
#
|
8
|
+
# Ein Spielbrett bestehend aus 65 Feldern.
|
9
9
|
class Board
|
10
|
-
|
11
10
|
# @!attribute [r] fields
|
12
|
-
# @note
|
13
|
-
# @return [
|
14
|
-
#
|
11
|
+
# @note Besser über die {#field} Methode auf Felder zugreifen.
|
12
|
+
# @return [Array<Field>] Ein Feld wird an der Position entsprechend seines
|
13
|
+
# Index im Array gespeichert.
|
15
14
|
attr_reader :fields
|
16
15
|
|
17
16
|
# Initializes the board
|
18
17
|
def initialize
|
19
|
-
@fields =
|
18
|
+
@fields = []
|
20
19
|
end
|
21
20
|
|
22
21
|
def to_s
|
23
|
-
fields.
|
22
|
+
fields.map { |f| f.type.value }.join(' ')
|
24
23
|
end
|
25
24
|
|
26
25
|
def ==(other)
|
27
|
-
fields.each_with_index do |
|
28
|
-
|
29
|
-
return false if field != other.field(x, y)
|
30
|
-
end
|
26
|
+
fields.each_with_index do |field, index|
|
27
|
+
return false if field != other.field(index)
|
31
28
|
end
|
32
29
|
true
|
33
30
|
end
|
34
31
|
|
35
32
|
def add_field(field)
|
36
|
-
fields[
|
37
|
-
end
|
38
|
-
|
39
|
-
# @return [Integer, Integer] The coordinates of the neighbor of the field
|
40
|
-
# specified by given coordinates in specified
|
41
|
-
# direction.
|
42
|
-
def get_neighbor(x, y, direction)
|
43
|
-
directions = {
|
44
|
-
even_row: {
|
45
|
-
Direction::RIGHT.key=> [+1, 0],
|
46
|
-
Direction::UP_RIGHT.key=>[+1, -1],
|
47
|
-
Direction::UP_LEFT.key=>[0, -1],
|
48
|
-
Direction::LEFT.key=>[-1, 0],
|
49
|
-
Direction::DOWN_LEFT.key=>[0, +1],
|
50
|
-
Direction::DOWN_RIGHT.key=>[+1, +1]
|
51
|
-
},
|
52
|
-
odd_row: {
|
53
|
-
Direction::RIGHT.key=> [+1, 0],
|
54
|
-
Direction::UP_RIGHT.key=> [ 0, -1],
|
55
|
-
Direction::UP_LEFT.key=> [-1, -1],
|
56
|
-
Direction::LEFT.key=> [-1, 0],
|
57
|
-
Direction::DOWN_LEFT.key=> [-1, +1],
|
58
|
-
Direction::DOWN_RIGHT.key=> [ 0, +1]
|
59
|
-
}
|
60
|
-
}
|
61
|
-
|
62
|
-
parity = y.odd? ? :odd_row : :even_row
|
63
|
-
dir = directions[parity][direction.key]
|
64
|
-
return x + dir[0], y + dir[1]
|
65
|
-
end
|
66
|
-
|
67
|
-
# @return [Field] The field in given direction with given distance from the
|
68
|
-
# field with given coordinates.
|
69
|
-
def get_in_direction(from_x, from_y, direction, distance = 1)
|
70
|
-
x = from_x
|
71
|
-
y = from_y
|
72
|
-
distance.times do
|
73
|
-
x, y = get_neighbor(x, y, direction)
|
74
|
-
end
|
75
|
-
if !field(x, y).nil?
|
76
|
-
return field(x, y)
|
77
|
-
else
|
78
|
-
raise FieldUnavailableException.new(x, y)
|
79
|
-
end
|
80
|
-
end
|
81
|
-
|
82
|
-
# @return [Array<Field>] A list of fields in given direction up to given
|
83
|
-
# distance from the field with given coordinates.
|
84
|
-
# The start field is excluded.
|
85
|
-
def get_all_in_direction(from_x, from_y, direction, distance = 1)
|
86
|
-
(1..distance).to_a.map do |i|
|
87
|
-
get_in_direction(
|
88
|
-
from_x, from_y, direction, i
|
89
|
-
)
|
90
|
-
end
|
33
|
+
@fields[field.index] = field
|
91
34
|
end
|
92
35
|
|
93
|
-
#
|
36
|
+
# Zugriff auf die Felder des Spielfeldes
|
94
37
|
#
|
95
|
-
# @param
|
96
|
-
# @
|
97
|
-
#
|
98
|
-
|
99
|
-
|
38
|
+
# @param index [Integer] Der Index des Feldes
|
39
|
+
# @return [Field] Das Feld mit dem gegebenen Index. Falls das Feld nicht
|
40
|
+
# exisitert (weil der Index ausserhalb von 0..64 liegt), wird ein neues
|
41
|
+
# Feld vom Typ INVALID zurückgegeben.
|
42
|
+
def field(index)
|
43
|
+
fields.fetch(index, Field.new(FieldType::INVALID, index))
|
100
44
|
end
|
101
45
|
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'typesafe_enum'
|
4
|
+
class CardType < TypesafeEnum::Base
|
5
|
+
# Nehme Karotten auf, oder leg sie ab
|
6
|
+
new :TAKE_OR_DROP_CARROTS
|
7
|
+
# Iß sofort einen Salat
|
8
|
+
new :EAT_SALAD
|
9
|
+
# Falle eine Position zurück
|
10
|
+
new :FALL_BACK
|
11
|
+
# Rücke eine Position vor
|
12
|
+
new :HURRY_AHEAD
|
13
|
+
end
|
@@ -1,65 +1,31 @@
|
|
1
1
|
# encoding: UTF-8
|
2
2
|
require_relative 'field_type'
|
3
3
|
|
4
|
-
#
|
4
|
+
# Ein Feld des Spielfelds. Ein Spielfeld ist durch den index eindeutig identifiziert.
|
5
|
+
# Das type Attribut gibt an, um welchen Feldtyp es sich handelt
|
5
6
|
class Field
|
6
7
|
# @!attribute [rw] type
|
7
|
-
# @return [FieldType]
|
8
|
+
# @return [FieldType] der Typ des Feldes
|
8
9
|
attr_accessor :type
|
9
|
-
# @!attribute [r] x
|
10
|
-
# @return [Integer] the field's x-coordinate
|
11
|
-
attr_reader :x
|
12
|
-
# @!attribute [r] y
|
13
|
-
# @return [Integer] the field's y-coordinate
|
14
|
-
attr_reader :y
|
15
|
-
|
16
|
-
# @!attribute [r] direction
|
17
|
-
# @return [Integer] the direction of the tile which the field belongs to
|
18
|
-
attr_reader :direction
|
19
|
-
|
20
10
|
# @!attribute [r] index
|
21
|
-
# @return [Integer]
|
11
|
+
# @return [Integer] der Index des Feldes (0 bis 64)
|
22
12
|
attr_reader :index
|
23
13
|
|
24
|
-
#
|
25
|
-
# @return [Integer] the points awarded to a player placed on this field additionally to points for current tile and passengers
|
26
|
-
attr_reader :points
|
27
|
-
|
28
|
-
# Initializer
|
14
|
+
# Konstruktor
|
29
15
|
#
|
30
|
-
# @param type [FieldType]
|
31
|
-
# @param
|
32
|
-
|
33
|
-
# @param index [Integer] index of tile
|
34
|
-
# @param direction [Integer] direction of tile
|
35
|
-
# @param points [Integer] points
|
36
|
-
def initialize(type, x, y, index, direction, points)
|
16
|
+
# @param type [FieldType] Feldtyp
|
17
|
+
# @param index [Integer] Index
|
18
|
+
def initialize(type, index)
|
37
19
|
self.type = type
|
38
|
-
@x = x
|
39
|
-
@y = y
|
40
20
|
@index = index
|
41
|
-
@direction = direction
|
42
|
-
@points = points
|
43
21
|
end
|
44
22
|
|
45
23
|
def ==(another_field)
|
46
24
|
return self.type == another_field.type &&
|
47
|
-
self.
|
48
|
-
self.y == another_field.y
|
49
|
-
end
|
50
|
-
|
51
|
-
# @return [Boolean] true if the field may never be moved onto.
|
52
|
-
def blocked?
|
53
|
-
[FieldType::BLOCKED,
|
54
|
-
FieldType::PASSENGER0,
|
55
|
-
FieldType::PASSENGER1,
|
56
|
-
FieldType::PASSENGER2,
|
57
|
-
FieldType::PASSENGER3,
|
58
|
-
FieldType::PASSENGER4,
|
59
|
-
FieldType::PASSENGER5].include? type
|
25
|
+
self.index == another_field.index
|
60
26
|
end
|
61
27
|
|
62
28
|
def to_s
|
63
|
-
return "
|
29
|
+
return "Feld ##{self.index}, Typ = #{self.type}"
|
64
30
|
end
|
65
31
|
end
|
@@ -1,30 +1,24 @@
|
|
1
|
-
# encoding:
|
1
|
+
# encoding: utf-8
|
2
2
|
|
3
3
|
require 'typesafe_enum'
|
4
|
-
#
|
5
|
-
#
|
6
|
-
#
|
7
|
-
# * PASSENGER0
|
8
|
-
# * PASSENGER1
|
9
|
-
# * PASSENGER2
|
10
|
-
# * PASSENGER3
|
11
|
-
# * PASSENGER4
|
12
|
-
# * PASSENGER5
|
13
|
-
# * SANDBANK
|
14
|
-
# * LOG
|
15
|
-
# * GOAL
|
16
|
-
# Access them with FieldType::WATER.
|
4
|
+
# Zahl- und Flaggenfelder Die veränderten Spielregeln sehen nur noch die
|
5
|
+
# Felder 1,2 vor. Die Positionsfelder 3 und 4 wurden in Möhrenfelder
|
6
|
+
# umgewandelt, und (1,5,6) sind jetzt Position-1-Felder.
|
17
7
|
class FieldType < TypesafeEnum::Base
|
18
|
-
new :
|
19
|
-
new :
|
20
|
-
|
21
|
-
new :
|
22
|
-
|
23
|
-
new :
|
24
|
-
|
25
|
-
new :
|
26
|
-
|
27
|
-
new :
|
28
|
-
|
29
|
-
|
8
|
+
new :POSITION_1, '1'
|
9
|
+
new :POSITION_2, '2'
|
10
|
+
# Igelfeld
|
11
|
+
new :HEDGEHOG, 'I'
|
12
|
+
# Salatfeld
|
13
|
+
new :SALAD, 'S'
|
14
|
+
# Karottenfeld
|
15
|
+
new :CARROT, 'C'
|
16
|
+
# Hasenfeld
|
17
|
+
new :HARE, 'H'
|
18
|
+
# außerhalb des Spielfeldes
|
19
|
+
new :INVALID, 'X'
|
20
|
+
# Zielfeld
|
21
|
+
new :GOAL, 'G'
|
22
|
+
# Startfeld
|
23
|
+
new :START, '0'
|
30
24
|
end
|
@@ -0,0 +1,378 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
|
3
|
+
require_relative 'field_type'
|
4
|
+
require_relative 'card_type'
|
5
|
+
|
6
|
+
# All methods which define the game rules. Needed for checking validity of moves
|
7
|
+
# and performing them.
|
8
|
+
class GameRules
|
9
|
+
|
10
|
+
# Berechnet wie viele Karotten für einen Zug der länge
|
11
|
+
# <code>moveCount</code> benötigt werden.
|
12
|
+
#
|
13
|
+
# @param moveCount Anzahl der Felder, um die bewegt wird
|
14
|
+
# @return Anzahl der benötigten Karotten
|
15
|
+
def self.calculate_carrots(moveCount)
|
16
|
+
(moveCount * (moveCount + 1)) / 2
|
17
|
+
end
|
18
|
+
|
19
|
+
# Berechnet, wieviele Züge mit <code>carrots</code> Karotten möglich sind.
|
20
|
+
#
|
21
|
+
# @param carrots maximal ausgegebene Karotten
|
22
|
+
# @return Felder um die maximal bewegt werden kann
|
23
|
+
def self.calculate_movable_fields(carrots)
|
24
|
+
# bei 30 Runden koennen nur 990 Karotten gesammelt werden
|
25
|
+
return 44 if carrots >= 990
|
26
|
+
return 0 if carrots < 1
|
27
|
+
(Math.sqrt(2 * carrots + 1/4) - 1/2).round
|
28
|
+
end
|
29
|
+
|
30
|
+
# Überprüft <code>Advance</code> Aktionen auf ihre Korrektheit. Folgende
|
31
|
+
# Spielregeln werden beachtet:
|
32
|
+
#
|
33
|
+
# - Der Spieler muss genügend Karotten für den Zug besitzen
|
34
|
+
# - Wenn das Ziel erreicht wird, darf der Spieler nach dem Zug maximal 10 Karotten übrig haben
|
35
|
+
# - Man darf nicht auf Igelfelder ziehen
|
36
|
+
# - Salatfelder dürfen nur betreten werden, wenn man noch Salate essen muss
|
37
|
+
# - Hasenfelder dürfen nur betreten werden, wenn man noch Hasenkarten ausspielen kann
|
38
|
+
#
|
39
|
+
# @param state GameState
|
40
|
+
# @param distance relativer Abstand zur aktuellen Position des Spielers
|
41
|
+
# @return [true, ''] falls ein Vorwärtszug möglich ist, [false, M] falls nicht, wobei M ein String mit einer Begruendung ist
|
42
|
+
def self.is_valid_to_advance(state, distance)
|
43
|
+
return false, 'Ein Vorwärtszug benötigt eine Mindestdistanz von einem Feld.' if distance <= 0
|
44
|
+
player = state.current_player
|
45
|
+
return false, 'Es muss ein Salat gegessen werden, bevor ein Vorwärtszug gemacht werden kann.' if must_eat_salad(state)
|
46
|
+
required_carrots = GameRules.calculate_carrots(distance)
|
47
|
+
return false, "Nicht genug Karotten, um #{distance} Felder vorwärts zu ziehen (Vorrat: #{player.carrots}, benötigt: #{required_carrots}" if (required_carrots > player.carrots)
|
48
|
+
new_position = player.index + distance
|
49
|
+
return false, 'Zielfeld wird von anderem Spieler besetzt.' if state.occupied_by_other_player?(state.field(new_position))
|
50
|
+
case state.field(new_position).type
|
51
|
+
when FieldType::INVALID
|
52
|
+
return false, "Zielfeld #{new_position} ist nicht vorhanden."
|
53
|
+
when FieldType::SALAD
|
54
|
+
return false, 'Ohne Salat darf ein Salatfeld nicht betreten werden.' if player.salads < 1
|
55
|
+
when FieldType::HARE
|
56
|
+
state2 = state.deep_clone
|
57
|
+
state2.set_last_action(Advance.new(distance))
|
58
|
+
state2.current_player.index = new_position
|
59
|
+
state2.current_player.carrots -= required_carrots
|
60
|
+
return false, 'Auf ein Hasenfeld darf nur gezogen werden, wenn eine Karte gespielt werden kann' unless can_play_any_card(state2)
|
61
|
+
when FieldType::GOAL
|
62
|
+
carrotsLeft = player.carrots - required_carrots
|
63
|
+
return false, "Auf das Zielfeld darf nur mit maximal 10 Karotten gezogen werden (es sind aber #{carrotsLeft} bei Erreichen des Zielfeldes)." unless carrotsLeft <= 10
|
64
|
+
return false, "Auf das Zielfeld darf nur ohne Salate gezogen werden (es sind aber #{player.salads} übrig)." unless player.salads == 0
|
65
|
+
when FieldType::HEDGEHOG
|
66
|
+
return false, 'Auf ein Igelfeld darf nicht vorwärts gezogen werden.'
|
67
|
+
when FieldType::CARROT, FieldType::POSITION_1, FieldType::START, FieldType::POSITION_2
|
68
|
+
return true, ''
|
69
|
+
else
|
70
|
+
raise "Unknown Type #{state.field(new_position).type.inspect}"
|
71
|
+
end
|
72
|
+
return true, ''
|
73
|
+
end
|
74
|
+
|
75
|
+
# Überprüft, ob ein Spieler aussetzen darf. Er darf dies, wenn kein anderer Zug möglich ist.
|
76
|
+
# @param state GameState
|
77
|
+
# @return true, falls der derzeitige Spieler keine andere Aktion machen kann.
|
78
|
+
def self.is_valid_to_skip(state)
|
79
|
+
return !GameRules.can_do_anything(state)
|
80
|
+
end
|
81
|
+
|
82
|
+
# Überprüft, ob ein Spieler einen Zug (keinen Aussetzug)
|
83
|
+
# @param state GameState
|
84
|
+
# @return true, falls ein Zug möglich ist.
|
85
|
+
def self.can_do_anything(state)
|
86
|
+
return can_play_any_card(state) || is_valid_to_fall_back(state)[0] ||
|
87
|
+
is_valid_to_exchange_carrots(state, 10)[0] ||
|
88
|
+
is_valid_to_exchange_carrots(state, -10)[0] ||
|
89
|
+
is_valid_to_eat(state)[0] || can_advance_to_any_field(state)
|
90
|
+
end
|
91
|
+
|
92
|
+
# Überprüft ob der derzeitige Spieler zu irgendeinem Feld einen Vorwärtszug machen kann.
|
93
|
+
# @param state GameState
|
94
|
+
# @return true, falls der Spieler irgendeinen Vorwärtszug machen kann
|
95
|
+
def self.can_advance_to_any_field(state)
|
96
|
+
fields = calculate_movable_fields(state.getCurrentPlayer().getCarrots())
|
97
|
+
(0..fields).to_a.any? do |i|
|
98
|
+
is_valid_to_advance(state, i)[0]
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
# Überprüft <code>EatSalad</code> Züge auf Korrektheit. Um einen Salat
|
103
|
+
# zu verzehren muss der Spieler sich:
|
104
|
+
#
|
105
|
+
# - auf einem Salatfeld befinden
|
106
|
+
# - noch mindestens einen Salat besitzen
|
107
|
+
# - vorher kein Salat auf diesem Feld verzehrt wurde
|
108
|
+
#
|
109
|
+
# @param state GameState
|
110
|
+
# @return [true, ''], falls ein Salad gegessen werden darf, [false, M] falls nicht, wobei M ein String mit dem Grund ist
|
111
|
+
def self.is_valid_to_eat(state)
|
112
|
+
return false, 'Salate dürfen nur auf Salatfeldern gegessen werden.' unless state.current_field.type == FieldType::SALAD
|
113
|
+
return false, 'Spieler hat keine Salate mehr zum Essen.' if state.current_player.salads < 1
|
114
|
+
return false, 'Spieler muss das Feld verlassen.' if player_must_advance(state)
|
115
|
+
return true, ''
|
116
|
+
end
|
117
|
+
|
118
|
+
# Überprüft ab der derzeitige Spieler im nächsten Zug einen Vorwärtszug machen muss.
|
119
|
+
# @param state GameState
|
120
|
+
# @return true, falls der derzeitige Spieler einen Vorwärtszug gemacht werden muss
|
121
|
+
def self.player_must_advance(state)
|
122
|
+
player = state.current_player
|
123
|
+
type = state.board.field(player.index).type
|
124
|
+
|
125
|
+
return true if (type == FieldType::HEDGEHOG || type == FieldType::START)
|
126
|
+
|
127
|
+
last_action = state.current_player.last_non_skip_action
|
128
|
+
|
129
|
+
if (!last_action.nil?)
|
130
|
+
if (last_action.instance_of? EatSalad)
|
131
|
+
return true
|
132
|
+
elsif (last_action.instance_of? Card)
|
133
|
+
# the player has to leave a rabbit field in next turn
|
134
|
+
if (last_action.type == CardType::EAT_SALAD)
|
135
|
+
return true
|
136
|
+
elsif (last_action.type == CardType::TAKE_OR_DROP_CARROTS) # the player has to leave the rabbit field
|
137
|
+
return true
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
return false
|
143
|
+
end
|
144
|
+
|
145
|
+
# Überprüft ob der derzeitige Spieler 10 Karotten nehmen oder abgeben kann.
|
146
|
+
# @param state GameState
|
147
|
+
# @param n 10 oder -10 je nach Fragestellung
|
148
|
+
# @return [true, ''], falls die durch n spezifizierte Aktion möglich ist, [false, M] falls nicht, wobei M ein String mit dem Grund ist
|
149
|
+
def self.is_valid_to_exchange_carrots(state, n)
|
150
|
+
player = state.current_player
|
151
|
+
return false, "Karotten können nur auf einem Karottenfeld #{n > 0 ? 'genommen' : 'abgegeben'} werden" if state.board.field(player.index).type != FieldType::CARROT
|
152
|
+
return true, '' if n == 10
|
153
|
+
return false, 'Gültige Karottenzahlen sind 10 und -10.' unless n == -10
|
154
|
+
return false, "Spieler hat keine 10 Karotten zum Abgeben (er hat #{player.carrots})." if player.carrots < 10
|
155
|
+
return true, ''
|
156
|
+
end
|
157
|
+
|
158
|
+
# Überprüft <code>FallBack</code> Züge auf Korrektheit
|
159
|
+
#
|
160
|
+
# @param state GameState
|
161
|
+
# @return [true, ''], falls der currentPlayer einen Rückzug machen darf, [false, M] falls nicht, wobei M ein String mit dem Grund ist.
|
162
|
+
def self.is_valid_to_fall_back(state)
|
163
|
+
return false, 'Spieler muss einen Salat fressen.' if must_eat_salad(state)
|
164
|
+
target_field = state.previous_field_of_type(
|
165
|
+
FieldType::HEDGEHOG, state.current_player.index
|
166
|
+
)
|
167
|
+
return false, 'Es gibt kein Igelfeld hinter dem Spieler.' if target_field.nil?
|
168
|
+
return false, 'Das Igelfeld hinter dem Spieler ist besetzt.' if state.occupied_by_other_player?(target_field)
|
169
|
+
return true, ''
|
170
|
+
end
|
171
|
+
|
172
|
+
# Überprüft ob der derzeitige Spieler die <code>FALL_BACK</code> Karte spielen darf.
|
173
|
+
# @param state GameState
|
174
|
+
# @return [true, ''], falls die <code>FALL_BACK</code> Karte gespielt werden darf, [false, M] falls nicht, wobei M ein String mit dem Grund ist.
|
175
|
+
def self.is_valid_to_play_fall_back(state)
|
176
|
+
player = state.current_player
|
177
|
+
return false, 'Spieler muss einen Vorwärtszug machen.' if player_must_advance(state)
|
178
|
+
return false, 'Karten können nur auf Hasenfeldern gespielt werden.' unless state.current_field.type == FieldType::HARE
|
179
|
+
return false, 'Nur der erste Spieler darf die FALL_BACK Karte spielen.' unless state.is_first(player)
|
180
|
+
return false, 'Spieler besitzt die Karte FALL_BACK nicht.' unless player.owns_card_of_type(CardType::FALL_BACK)
|
181
|
+
|
182
|
+
next_pos = state.other_player.index - 1
|
183
|
+
|
184
|
+
case state.field(next_pos).type
|
185
|
+
when FieldType::INVALID
|
186
|
+
return false, 'Durch Spielen der FALL_BACK Karte darf man nicht auf einem nicht vorhandenen Feld landen (also vor dem Start).'
|
187
|
+
when FieldType::HEDGEHOG
|
188
|
+
return false, 'Durch Spielen der FALL_BACK Karte darf man nicht auf einem Igelfeld landen.'
|
189
|
+
when FieldType::SALAD
|
190
|
+
return false, 'Spieler käme durch Spielen der FALL_BACK Kart auf ein Salatfeld, hat aber keine Salate.' if player.salads < 1
|
191
|
+
when FieldType::HARE
|
192
|
+
state2 = state.deep_clone
|
193
|
+
state2.set_last_action(Card.new(CardType::HURRY_AHEAD))
|
194
|
+
state2.current_player.cards.delete(CardType::FALL_BACK)
|
195
|
+
return false, 'Spieler käme durch Spielen der FALL_BACK Kart auf ein Hasenfeld, kann aber dann keine weitere Karte mehr spielen.' unless can_play_any_card(state2)
|
196
|
+
when FieldType::START
|
197
|
+
when FieldType::CARROT
|
198
|
+
when FieldType::POSITION_1
|
199
|
+
when FieldType::POSITION_2
|
200
|
+
return true, ''
|
201
|
+
when FieldType::GOAL
|
202
|
+
raise 'Player got onto goal by playing a fall back card. This should never happen.'
|
203
|
+
else
|
204
|
+
raise "Unknown Type #{state.field(next_pos).type.inspect}"
|
205
|
+
end
|
206
|
+
return true, ''
|
207
|
+
end
|
208
|
+
|
209
|
+
# Überprüft ob der derzeitige Spieler die <code>HURRY_AHEAD</code> Karte spielen darf.
|
210
|
+
# @param state GameState
|
211
|
+
# @return [true, ''], falls die <code>HURRY_AHEAD</code> Karte gespielt werden darf, [false, M], falls nicht, wobei M ein String mit dem Grund ist.
|
212
|
+
def self.is_valid_to_play_hurry_ahead(state)
|
213
|
+
player = state.current_player
|
214
|
+
return false, 'Spieler muss einen Vorwärtszug machen.' if player_must_advance(state)
|
215
|
+
return false, 'Karten können nur auf Hasenfeldern gespielt werden.' unless state.current_field.type == FieldType::HARE
|
216
|
+
return false, 'Nur der zweite Spieler darf die HURRY_AHEAD Karte spielen.' unless state.is_second(player)
|
217
|
+
return false, 'Spieler besitzt die Karte HURRY_AHEAD nicht.' unless player.owns_card_of_type(CardType::HURRY_AHEAD)
|
218
|
+
|
219
|
+
o = state.other_player
|
220
|
+
next_pos = o.index + 1
|
221
|
+
|
222
|
+
case state.field(next_pos).type
|
223
|
+
when FieldType::INVALID
|
224
|
+
return false, 'Durch Spielen der HURRY_AHEAD Karte darf man nicht auf einem nicht vorhandenen Feld landen (also nach dem Ziel).'
|
225
|
+
when FieldType::HEDGEHOG
|
226
|
+
return false, 'Durch Spielen der HURRY_AHEAD Karte darf man nicht auf einem Igelfeld landen.'
|
227
|
+
when FieldType::SALAD
|
228
|
+
return false, 'Spieler käme durch Spielen der HURRY_AHEAD Kart auf ein Salatfeld, hat aber keine Salate.' if player.salads < 1
|
229
|
+
when FieldType::HARE
|
230
|
+
state2 = state.deep_clone
|
231
|
+
state2.set_last_action(Card.new(CardType::HURRY_AHEAD))
|
232
|
+
state2.current_player.cards.delete(CardType::HURRY_AHEAD)
|
233
|
+
return false, 'Spieler käme durch Spielen der HURRY_AHEAD Kart auf ein Hasenfeld, kann aber dann keine weitere Karte mehr spielen.' unless can_play_any_card(state2)
|
234
|
+
when FieldType::GOAL
|
235
|
+
return false, 'Spieler käme durch Spielen der HURRY_AHEAD Kart ins Ziel, darf es aber nicht betreten (entweder noch Salate oder mehr als 10 Karotten).' unless can_enter_goal(state)
|
236
|
+
when FieldType::CARROT
|
237
|
+
when FieldType::POSITION_1
|
238
|
+
when FieldType::POSITION_2
|
239
|
+
return true, ''
|
240
|
+
when FieldType::START
|
241
|
+
raise 'Player got onto start field by playing a hurry ahead card. This should never happen.'
|
242
|
+
else
|
243
|
+
raise "Unknown Type #{state.field(next_pos).type.inspect}"
|
244
|
+
end
|
245
|
+
return true, ''
|
246
|
+
end
|
247
|
+
|
248
|
+
# Überprüft ob der derzeitige Spieler die <code>TAKE_OR_DROP_CARROTS</code> Karte spielen darf.
|
249
|
+
# @param state GameState
|
250
|
+
# @param n 20 für nehmen, -20 für abgeben, 0 für nichts tun
|
251
|
+
# @return [true, ''], falls die <code>TAKE_OR_DROP_CARROTS</code> Karte gespielt werden darf, [false, M], falls nicht, wobei M ein String mit dem Grund ist.
|
252
|
+
def self.is_valid_to_play_take_or_drop_carrots(state, n)
|
253
|
+
player = state.current_player
|
254
|
+
return false, 'Spieler muss einen Vorwärtszug machen.' if player_must_advance(state)
|
255
|
+
return false, 'Karten können nur auf Hasenfeldern gespielt werden.' unless state.current_field.type == FieldType::HARE
|
256
|
+
return false, 'Spieler besitzt die Karte TAKE_OR_DROP_CARROTS nicht.' unless player.owns_card_of_type(CardType::TAKE_OR_DROP_CARROTS)
|
257
|
+
return false, "#{n} ist keine erlaubte Anzahl beim Spielen der Karte TAKE_OR_DROP_CARROTS (erlaubt sind 20, -20 und 0)." unless [20, -20, 0].include?(n)
|
258
|
+
return true, '' if n >= 0
|
259
|
+
# at this point, n has to be -20
|
260
|
+
return false, "Spieler hat keine 20 Karotten zum Abgeben (er hat #{player.carrots})." if player.carrots < 20
|
261
|
+
return true, ''
|
262
|
+
end
|
263
|
+
|
264
|
+
# Überprüft ob der derzeitige Spieler die <code>EAT_SALAD</code> Karte spielen darf.
|
265
|
+
# @param state GameState
|
266
|
+
# @return (true, ''), falls die <code>EAT_SALAD</code> Karte gespielt werden darf, (false, M) falls nicht, wobei M ein String mit dem Grund ist
|
267
|
+
def self.is_valid_to_play_eat_salad(state)
|
268
|
+
player = state.current_player
|
269
|
+
return false, 'Es muss vorwärts gezogen werden.' if player_must_advance(state)
|
270
|
+
return false, 'Karten können nur auf Hasenfeldern gespielt werden.' unless state.current_field.type == FieldType::HARE
|
271
|
+
return false, 'Spieler besitzt die Karte nicht.' unless player.owns_card_of_type(CardType::EAT_SALAD)
|
272
|
+
return false, 'Spieler hat keine Salate zum Essen' if player.salads < 1
|
273
|
+
return true, ''
|
274
|
+
end
|
275
|
+
|
276
|
+
# Überprüft ob der derzeitige Spieler irgendeine Karte spielen kann.
|
277
|
+
# TAKE_OR_DROP_CARROTS wird nur mit 20 überprüft
|
278
|
+
# @param state GameState
|
279
|
+
# @return true, falls das Spielen einer Karte möglich ist
|
280
|
+
def self.can_play_any_card(state)
|
281
|
+
valid = false
|
282
|
+
player = state.current_player
|
283
|
+
|
284
|
+
player.cards.any? do |card|
|
285
|
+
case card
|
286
|
+
when CardType::EAT_SALAD
|
287
|
+
is_valid_to_play_eat_salad(state)[0]
|
288
|
+
when CardType::FALL_BACK
|
289
|
+
is_valid_to_play_fall_back(state)[0]
|
290
|
+
when CardType::HURRY_AHEAD
|
291
|
+
is_valid_to_play_hurry_ahead(state)[0]
|
292
|
+
when CardType::TAKE_OR_DROP_CARROTS
|
293
|
+
is_valid_to_play_take_or_drop_carrots(state, 20)[0]
|
294
|
+
else
|
295
|
+
raise "Unknown CardType " + card
|
296
|
+
end
|
297
|
+
end
|
298
|
+
end
|
299
|
+
|
300
|
+
# Überprüft ob der derzeitige Spieler die Karte spielen kann.
|
301
|
+
# @param state
|
302
|
+
# @param c Karte die gespielt werden soll
|
303
|
+
# @param n Parameter mit dem TAKE_OR_DROP_CARROTS überprüft wird, default 0
|
304
|
+
# @return true, falls das Spielen der entsprechenden karte möglich ist
|
305
|
+
def self.is_valid_to_play_card(state, c, n = 0)
|
306
|
+
case c
|
307
|
+
when CardType::EAT_SALAD
|
308
|
+
isValidToPlayEatSalad(state)[0]
|
309
|
+
when CardType::FALL_BACK
|
310
|
+
is_valid_to_play_fall_back(state)[0]
|
311
|
+
when CardType::HURRY_AHEAD
|
312
|
+
is_valid_to_play_hurry_ahead(state)[0]
|
313
|
+
when CardType::TAKE_OR_DROP_CARROTS
|
314
|
+
is_valid_to_play_take_or_drop_carrots(state, n)[0]
|
315
|
+
else
|
316
|
+
raise "Unknown CardType " + c
|
317
|
+
end
|
318
|
+
end
|
319
|
+
|
320
|
+
def self.must_eat_salad(state)
|
321
|
+
player = state.current_player
|
322
|
+
# check whether player just moved to salad field and must eat salad
|
323
|
+
field = state.board.field(player.index)
|
324
|
+
if field.type == FieldType::SALAD
|
325
|
+
if player.last_non_skip_action&.instance_of?(Advance)
|
326
|
+
return true
|
327
|
+
elsif player.last_non_skip_action&.instance_of?(Card)
|
328
|
+
card_action = player.last_non_skip_action
|
329
|
+
if card_action.card_type == CardType::FALL_BACK ||
|
330
|
+
card_action.card_type == CardType::HURRY_AHEAD
|
331
|
+
return true
|
332
|
+
end
|
333
|
+
end
|
334
|
+
end
|
335
|
+
return false
|
336
|
+
end
|
337
|
+
|
338
|
+
# TODO difference isValidToPlayCard
|
339
|
+
# @param state
|
340
|
+
# @return
|
341
|
+
def self.can_play_card(state)
|
342
|
+
player = state.getCurrentPlayer()
|
343
|
+
canPlayCard = state.getTypeAt(player.getFieldIndex()).equals(FieldType.HARE)
|
344
|
+
player.getCards().each do |card|
|
345
|
+
canPlayCard = canPlayCard || is_valid_to_play_card(state, card, 0)
|
346
|
+
end
|
347
|
+
return canPlayCard
|
348
|
+
end
|
349
|
+
|
350
|
+
# TODO difference isVAlidTOMove
|
351
|
+
# @param state
|
352
|
+
# @return
|
353
|
+
def self.can_move(state)
|
354
|
+
can_move = false
|
355
|
+
max_distance = GameRules.calculate_movable_fields(state.getCurrentPlayer().getCarrots())
|
356
|
+
(1..max_distance).to_a.each do |i|
|
357
|
+
can_move = can_move || isValidToAdvance(state, i)
|
358
|
+
end
|
359
|
+
return can_move
|
360
|
+
end
|
361
|
+
|
362
|
+
# Überprüft ob eine Karte gespielt werden muss. Sollte nach einem
|
363
|
+
# Zug eines Spielers immer false sein, ansonsten ist Zug ungültig.
|
364
|
+
# @param state derzeitiger GameState
|
365
|
+
def self.must_play_card(state)
|
366
|
+
state.current_player.must_play_card
|
367
|
+
end
|
368
|
+
|
369
|
+
|
370
|
+
# Überprüft ob ein der derzeitige Spieler das Ziel betreten darf
|
371
|
+
# @param state GameState
|
372
|
+
# @return
|
373
|
+
def self.can_enter_goal(state)
|
374
|
+
player = state.current_player
|
375
|
+
player.carrots <= 10 && player.salads == 0
|
376
|
+
end
|
377
|
+
|
378
|
+
end
|