software_challenge_client 20.2.2 → 21.0.2
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 +1 -0
- data/.ruby-version +1 -1
- data/.vscode/launch.json +41 -0
- data/.vscode/settings.json +10 -0
- data/Dockerfile +1 -1
- data/Gemfile +1 -0
- data/Guardfile +1 -0
- data/README.md +4 -3
- data/RELEASES.md +20 -0
- data/Rakefile +4 -4
- data/example/client.rb +6 -9
- data/example/main.rb +9 -9
- data/lib/software_challenge_client.rb +26 -23
- data/lib/software_challenge_client/board.rb +99 -34
- data/lib/software_challenge_client/client_interface.rb +1 -0
- data/lib/software_challenge_client/color.rb +16 -0
- data/lib/software_challenge_client/condition.rb +4 -1
- data/lib/software_challenge_client/coordinate_set.rb +92 -0
- data/lib/software_challenge_client/coordinates.rb +45 -0
- data/lib/software_challenge_client/debug_hint.rb +1 -0
- data/lib/software_challenge_client/field.rb +21 -53
- data/lib/software_challenge_client/game_rule_logic.rb +257 -332
- data/lib/software_challenge_client/game_state.rb +86 -68
- data/lib/software_challenge_client/has_hints.rb +1 -1
- data/lib/software_challenge_client/invalid_move_exception.rb +1 -0
- data/lib/software_challenge_client/logging.rb +1 -0
- data/lib/software_challenge_client/network.rb +1 -1
- data/lib/software_challenge_client/piece.rb +43 -14
- data/lib/software_challenge_client/piece_shape.rb +109 -0
- data/lib/software_challenge_client/player.rb +7 -6
- data/lib/software_challenge_client/player_type.rb +14 -0
- data/lib/software_challenge_client/protocol.rb +83 -75
- data/lib/software_challenge_client/rotation.rb +22 -0
- data/lib/software_challenge_client/runner.rb +2 -1
- data/lib/software_challenge_client/set_move.rb +13 -4
- data/lib/software_challenge_client/skip_move.rb +5 -0
- data/lib/software_challenge_client/util/constants.rb +3 -4
- data/lib/software_challenge_client/version.rb +2 -1
- data/lib/update_client_module.sh +15 -0
- data/software_challenge_client.gemspec +15 -13
- metadata +54 -36
- data/lib/software_challenge_client/cube_coordinates.rb +0 -23
- data/lib/software_challenge_client/direction.rb +0 -55
- data/lib/software_challenge_client/drag_move.rb +0 -19
- data/lib/software_challenge_client/line_direction.rb +0 -15
- data/lib/software_challenge_client/piece_type.rb +0 -18
- data/lib/software_challenge_client/player_color.rb +0 -25
@@ -1,4 +1,5 @@
|
|
1
1
|
# encoding: UTF-8
|
2
|
+
# frozen_string_literal: true
|
2
3
|
|
3
4
|
# Ein Spieler
|
4
5
|
class Player
|
@@ -6,15 +7,15 @@ class Player
|
|
6
7
|
# @return [String] der Name des Spielers, hat keine Auswirkungen auf das Spiel
|
7
8
|
attr_reader :name
|
8
9
|
|
9
|
-
# @!attribute [r]
|
10
|
-
# @return [
|
11
|
-
attr_reader :
|
10
|
+
# @!attribute [r] type
|
11
|
+
# @return [PlayerType] erster (PlayerType::ONE) oder zweiter (PlayerType::TWO) Spieler
|
12
|
+
attr_reader :type
|
12
13
|
|
13
14
|
# Konstruktor
|
14
|
-
# @param
|
15
|
+
# @param type [PlayerType] Erster oder zweiter
|
15
16
|
# @param name [String] Name
|
16
|
-
def initialize(
|
17
|
-
@
|
17
|
+
def initialize(type, name)
|
18
|
+
@type = type
|
18
19
|
@name = name
|
19
20
|
end
|
20
21
|
|
@@ -3,7 +3,6 @@
|
|
3
3
|
require 'socket'
|
4
4
|
require_relative 'board'
|
5
5
|
require_relative 'set_move'
|
6
|
-
require_relative 'drag_move'
|
7
6
|
require_relative 'skip_move'
|
8
7
|
require_relative 'player'
|
9
8
|
require_relative 'network'
|
@@ -41,7 +40,7 @@ class Protocol
|
|
41
40
|
#
|
42
41
|
# @param text [String] the xml-string that will be parsed
|
43
42
|
def process_string(text)
|
44
|
-
logger.debug "Parse XML:\n#{text}\n----END XML"
|
43
|
+
#logger.debug "Parse XML:\n#{text}\n----END XML"
|
45
44
|
begin
|
46
45
|
REXML::Document.parse_stream(text, self)
|
47
46
|
rescue REXML::ParseException => e
|
@@ -50,7 +49,6 @@ class Protocol
|
|
50
49
|
end
|
51
50
|
end
|
52
51
|
|
53
|
-
|
54
52
|
# called when text is encountered
|
55
53
|
def text(text)
|
56
54
|
@context[:last_text] = text
|
@@ -63,6 +61,27 @@ class Protocol
|
|
63
61
|
case name
|
64
62
|
when 'board'
|
65
63
|
logger.debug @gamestate.board.to_s
|
64
|
+
when 'color'
|
65
|
+
if @context[:color] == :ordered_colors
|
66
|
+
@gamestate.ordered_colors << Color.to_a.find {|s| s.key == @context[:last_text].to_sym }
|
67
|
+
end
|
68
|
+
when 'shape'
|
69
|
+
case @context[:piece_target]
|
70
|
+
when :blue_shapes
|
71
|
+
last = @context[:last_text]
|
72
|
+
arr = PieceShape.to_a
|
73
|
+
shape = arr.find {|s| s.key == @context[:last_text].to_sym }
|
74
|
+
@gamestate.undeployed_blue_pieces << shape
|
75
|
+
when :yellow_shapes
|
76
|
+
shape = PieceShape.to_a.find {|s| s.key == @context[:last_text].to_sym }
|
77
|
+
@gamestate.undeployed_yellow_pieces << shape
|
78
|
+
when :red_shapes
|
79
|
+
shape = PieceShape.to_a.find {|s| s.key == @context[:last_text].to_sym }
|
80
|
+
@gamestate.undeployed_red_pieces << shape
|
81
|
+
when :green_shapes
|
82
|
+
shape = PieceShape.to_a.find {|s| s.key == @context[:last_text].to_sym }
|
83
|
+
@gamestate.undeployed_green_pieces << shape
|
84
|
+
end
|
66
85
|
end
|
67
86
|
end
|
68
87
|
|
@@ -97,59 +116,69 @@ class Protocol
|
|
97
116
|
when 'state'
|
98
117
|
logger.debug 'new gamestate'
|
99
118
|
@gamestate = GameState.new
|
119
|
+
@gamestate.current_color_index = attrs['currentColorIndex'].to_i
|
100
120
|
@gamestate.turn = attrs['turn'].to_i
|
101
|
-
@gamestate.
|
102
|
-
@gamestate.
|
103
|
-
logger.debug "Turn: #{@gamestate.turn}"
|
104
|
-
when '
|
105
|
-
logger.debug 'new
|
106
|
-
player =
|
107
|
-
if player.color != PlayerColor::RED
|
108
|
-
throw new IllegalArgumentException("expected #{PlayerColor::RED} Player but got #{player.color}")
|
109
|
-
end
|
121
|
+
@gamestate.round = attrs['round'].to_i
|
122
|
+
@gamestate.start_piece = PieceShape.to_a.find {|s| s.key == attrs['startPiece'].to_sym }
|
123
|
+
logger.debug "Round: #{@gamestate.round}, Turn: #{@gamestate.turn}"
|
124
|
+
when 'first'
|
125
|
+
logger.debug 'new first player'
|
126
|
+
player = Player.new(PlayerType::ONE, attrs['displayName'])
|
110
127
|
@gamestate.add_player(player)
|
111
128
|
@context[:player] = player
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
throw new IllegalArgumentException("expected #{PlayerColor::BLUE} Player but got #{player.color}")
|
117
|
-
end
|
129
|
+
@context[:color] = :one
|
130
|
+
when 'second'
|
131
|
+
logger.debug 'new second player'
|
132
|
+
player = Player.new(PlayerType::TWO, attrs['displayName'])
|
118
133
|
@gamestate.add_player(player)
|
119
134
|
@context[:player] = player
|
135
|
+
@context[:color] = :two
|
136
|
+
when 'orderedColors'
|
137
|
+
@context[:color] = :ordered_colors
|
138
|
+
@gamestate.ordered_colors = []
|
120
139
|
when 'board'
|
121
140
|
logger.debug 'new board'
|
122
|
-
@gamestate.board = Board.new
|
141
|
+
@gamestate.board = Board.new()
|
123
142
|
when 'field'
|
124
143
|
x = attrs['x'].to_i
|
125
144
|
y = attrs['y'].to_i
|
126
|
-
|
127
|
-
field = Field.new(x, y,
|
145
|
+
color = Color.find_by_key(attrs['content'].to_sym)
|
146
|
+
field = Field.new(x, y, color)
|
128
147
|
@gamestate.board.add_field(field)
|
129
148
|
@context[:piece_target] = :field
|
130
149
|
@context[:field] = field
|
150
|
+
when 'blueShapes'
|
151
|
+
@context[:piece_target] = :blue_shapes
|
152
|
+
@gamestate.undeployed_blue_pieces = []
|
153
|
+
when 'yellowShapes'
|
154
|
+
@context[:piece_target] = :yellow_shapes
|
155
|
+
@gamestate.undeployed_yellow_pieces = []
|
156
|
+
when 'redShapes'
|
157
|
+
@context[:piece_target] = :red_shapes
|
158
|
+
@gamestate.undeployed_red_pieces = []
|
159
|
+
when 'greenShapes'
|
160
|
+
@context[:piece_target] = :green_shapes
|
161
|
+
@gamestate.undeployed_green_pieces = []
|
131
162
|
when 'piece'
|
132
|
-
|
133
|
-
|
134
|
-
|
163
|
+
color = Color.find_by_key(attrs['color'].to_sym)
|
164
|
+
kind = PieceShape.find_by_key(attrs['kind'].to_sym)
|
165
|
+
rotation = Rotation.find_by_key(attrs['rotation'].to_sym)
|
166
|
+
is_flipped = attrs['isFlipped'].downcase == "true"
|
167
|
+
piece = Piece.new(color, kind, rotation, is_flipped, Coordinates.origin)
|
135
168
|
case @context[:piece_target]
|
136
|
-
when :
|
137
|
-
@context[:field].add_piece(piece)
|
138
|
-
when :undeployed_red_pieces
|
139
|
-
@gamestate.undeployed_red_pieces << piece
|
140
|
-
when :undeployed_blue_pieces
|
169
|
+
when :blue_shapes
|
141
170
|
@gamestate.undeployed_blue_pieces << piece
|
171
|
+
when :yellow_shapes
|
172
|
+
@gamestate.undeployed_yellow_pieces << piece
|
173
|
+
when :red_shapes
|
174
|
+
@gamestate.green_red_pieces << piece
|
175
|
+
when :green_shapes
|
176
|
+
@gamestate.undeployed_green_pieces << piece
|
142
177
|
when :last_move
|
143
178
|
@context[:last_move_piece] = piece
|
144
179
|
else
|
145
180
|
raise "unknown piece target #{@context[:piece_target]}"
|
146
181
|
end
|
147
|
-
when 'undeployedRedPieces'
|
148
|
-
@context[:piece_target] = :undeployed_red_pieces
|
149
|
-
@gamestate.undeployed_red_pieces = []
|
150
|
-
when 'undeployedBluePieces'
|
151
|
-
@context[:piece_target] = :undeployed_blue_pieces
|
152
|
-
@gamestate.undeployed_blue_pieces = []
|
153
182
|
when 'lastMove'
|
154
183
|
type = attrs['class']
|
155
184
|
if type == 'skipmove'
|
@@ -158,24 +187,24 @@ class Protocol
|
|
158
187
|
@context[:last_move_type] = type
|
159
188
|
@context[:piece_target] = :last_move
|
160
189
|
end
|
161
|
-
when '
|
162
|
-
@context[:
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
@gamestate.last_move = SetMove.new(
|
168
|
-
when 'dragmove'
|
169
|
-
@gamestate.last_move = SetMove.new(@context[:last_move_start], destination)
|
190
|
+
when 'position'
|
191
|
+
case @context[:piece_target]
|
192
|
+
when :last_move
|
193
|
+
x = attrs['x'].to_i
|
194
|
+
y = attrs['y'].to_i
|
195
|
+
piece = @context[:last_move_piece]
|
196
|
+
@gamestate.last_move = SetMove.new(Piece.new(piece.color, piece.kind, piece.rotation, piece.is_flipped, Coordinates.new(x, y)))
|
170
197
|
end
|
198
|
+
when 'startColor'
|
199
|
+
@gamestate.start_color = Color::BLUE
|
171
200
|
when 'winner'
|
172
|
-
|
173
|
-
#winning_player = parsePlayer(attrs)
|
174
|
-
|
201
|
+
# TODO
|
202
|
+
# winning_player = parsePlayer(attrs)
|
203
|
+
# @gamestate.condition = Condition.new(winning_player, @gamestate.condition.reason)
|
175
204
|
when 'score'
|
176
205
|
# TODO
|
177
206
|
# there are two score tags in the result, but reason attribute should be equal on both
|
178
|
-
|
207
|
+
# @gamestate.condition = Condition.new(@gamestate.condition.winner, attrs['reason'])
|
179
208
|
when 'left'
|
180
209
|
logger.debug 'got left event, terminating'
|
181
210
|
@network.disconnect
|
@@ -185,17 +214,6 @@ class Protocol
|
|
185
214
|
end
|
186
215
|
end
|
187
216
|
|
188
|
-
# Converts XML attributes for a Player to a new Player object
|
189
|
-
#
|
190
|
-
# @param attributes [Hash] Attributes for the new Player.
|
191
|
-
# @return [Player] The created Player object.
|
192
|
-
def parsePlayer(attributes)
|
193
|
-
Player.new(
|
194
|
-
PlayerColor.find_by_key(attributes['color'].to_sym),
|
195
|
-
attributes['displayName']
|
196
|
-
)
|
197
|
-
end
|
198
|
-
|
199
217
|
# send a xml document
|
200
218
|
#
|
201
219
|
# @param document [REXML::Document] the document, that will be send to the connected server
|
@@ -205,7 +223,7 @@ class Protocol
|
|
205
223
|
|
206
224
|
# send a string
|
207
225
|
#
|
208
|
-
# @param
|
226
|
+
# @param string [String] The string that will be send to the connected server.
|
209
227
|
def sendString(string)
|
210
228
|
@network.sendString("<room roomId=\"#{@roomId}\">#{string}</room>")
|
211
229
|
end
|
@@ -228,26 +246,17 @@ class Protocol
|
|
228
246
|
# structures.
|
229
247
|
case move
|
230
248
|
when SetMove
|
231
|
-
builder.data(class: '
|
232
|
-
data.piece(
|
233
|
-
|
234
|
-
data.destination(x: d.x, y: d.y, z: d.z)
|
235
|
-
move.hints.each do |hint|
|
236
|
-
data.hint(content: hint.content)
|
249
|
+
builder.data(class: 'sc.plugin2021.SetMove') do |data|
|
250
|
+
data.piece(color: move.piece.color, kind: move.piece.kind, rotation: move.piece.rotation, isFlipped: move.piece.is_flipped) do |piece|
|
251
|
+
piece.position(x: move.piece.position.x, y: move.piece.position.y)
|
237
252
|
end
|
238
|
-
end
|
239
|
-
when DragMove
|
240
|
-
builder.data(class: 'dragmove') do |data|
|
241
|
-
s = move.start
|
242
|
-
data.start(x: s.x, y: s.y, z: s.z)
|
243
|
-
d = move.destination
|
244
|
-
data.destination(x: d.x, y: d.y, z: d.z)
|
245
253
|
move.hints.each do |hint|
|
246
254
|
data.hint(content: hint.content)
|
247
255
|
end
|
248
256
|
end
|
249
257
|
when SkipMove
|
250
|
-
builder.data(class: '
|
258
|
+
builder.data(class: 'sc.plugin2021.SkipMove') do |data|
|
259
|
+
data.color(@gamestate.current_color.key.to_s)
|
251
260
|
move.hints.each do |hint|
|
252
261
|
data.hint(content: hint.content)
|
253
262
|
end
|
@@ -255,5 +264,4 @@ class Protocol
|
|
255
264
|
end
|
256
265
|
builder.target!
|
257
266
|
end
|
258
|
-
|
259
267
|
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'typesafe_enum'
|
4
|
+
|
5
|
+
# Die Drehung eines Steins
|
6
|
+
class Rotation < TypesafeEnum::Base
|
7
|
+
new :NONE, 0
|
8
|
+
new :RIGHT, 1
|
9
|
+
new :MIRROR, 2
|
10
|
+
new :LEFT, 3
|
11
|
+
|
12
|
+
# Summiere beide Rotationen auf.
|
13
|
+
# (Die resultierende Rotation hat den gleichen Effekt wie die beiden Rotationen einzeln).
|
14
|
+
def rotate(rotation)
|
15
|
+
Rotation.to_a[(value + rotation.value) % Rotation.size]
|
16
|
+
end
|
17
|
+
|
18
|
+
# Gibt den rotation namen zurück
|
19
|
+
def to_s
|
20
|
+
self.key.to_s
|
21
|
+
end
|
22
|
+
end
|
@@ -1,4 +1,5 @@
|
|
1
1
|
# encoding: UTF-8
|
2
|
+
# frozen_string_literal: true
|
2
3
|
require_relative 'board'
|
3
4
|
require_relative 'client_interface'
|
4
5
|
require_relative 'network'
|
@@ -8,7 +9,7 @@ class Runner
|
|
8
9
|
include Logging
|
9
10
|
|
10
11
|
def initialize(host, port, client, reservation = nil)
|
11
|
-
logger.info 'Software Challenge
|
12
|
+
logger.info 'Software Challenge 2021'
|
12
13
|
logger.info 'Ruby Client'
|
13
14
|
logger.info "Host: #{host}"
|
14
15
|
logger.info "Port: #{port}"
|
@@ -1,15 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require_relative 'has_hints'
|
2
4
|
|
5
|
+
# Ein SetMove platziert einen Stein auf dem Spielbrett
|
3
6
|
class SetMove
|
4
|
-
|
5
7
|
include HasHints
|
6
8
|
|
7
9
|
attr_reader :piece
|
8
|
-
attr_reader :destination
|
9
10
|
|
10
|
-
|
11
|
+
# Erstellt ein neuen leeren Legezug.
|
12
|
+
def initialize(piece)
|
11
13
|
@piece = piece
|
12
|
-
@destination = destination
|
13
14
|
@hints = []
|
14
15
|
end
|
16
|
+
|
17
|
+
def ==(other)
|
18
|
+
piece == other.piece
|
19
|
+
end
|
20
|
+
|
21
|
+
def to_s
|
22
|
+
"SetMove(#{piece}"
|
23
|
+
end
|
15
24
|
end
|
@@ -1,7 +1,12 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require_relative 'has_hints'
|
4
|
+
|
5
|
+
# Ein SkipMove ziegt an, dass die aktuelle Farbe keinen Stein platzieren will
|
2
6
|
class SkipMove
|
3
7
|
include HasHints
|
4
8
|
|
9
|
+
# Erstellt ein neuen leeren Aussetzzug.
|
5
10
|
def initialize
|
6
11
|
@hints = []
|
7
12
|
end
|
@@ -4,8 +4,7 @@
|
|
4
4
|
# Konstanten zum aktuellen Spiel.
|
5
5
|
module Constants
|
6
6
|
ROUND_LIMIT = 30 # Rundenbegrenzung. Nach Ende der angegebenen Runde endet auch das Spiel.
|
7
|
-
GAME_IDENTIFIER = '
|
8
|
-
|
9
|
-
|
10
|
-
SHIFT = ((BOARD_SIZE - 1) / 2)
|
7
|
+
GAME_IDENTIFIER = 'swc_2021_blokus' # Der Identifikator des Spiels. Für die Kommunikation mit dem Spielserver.
|
8
|
+
BOARD_SIZE = 20 # Seitenlänge des Spielbretts in Feldern
|
9
|
+
TOTAL_PIECE_SHAPES = 21
|
11
10
|
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
#!/bin/bash
|
2
|
+
# This creates a entrypoint for the gem named 'software_challenge_client.rb'
|
3
|
+
# which includes all ruby files under lib. Making sure that everthing is
|
4
|
+
# included after some files were added or removed (in the process of updating
|
5
|
+
# the gem for a new game).
|
6
|
+
shopt -s globstar || exit 1
|
7
|
+
cd lib || exit 1
|
8
|
+
FILENAME='software_challenge_client.rb'
|
9
|
+
|
10
|
+
echo "# frozen_string_literal: true
|
11
|
+
module SoftwareChallengeClient" > $FILENAME
|
12
|
+
for file in software_challenge_client/**/*.rb; do
|
13
|
+
echo " require '${file}'"
|
14
|
+
done >> $FILENAME;
|
15
|
+
echo "end" >> $FILENAME
|
@@ -1,39 +1,41 @@
|
|
1
1
|
# coding: utf-8
|
2
|
+
# frozen_string_literal: true
|
2
3
|
lib = File.expand_path('../lib', __FILE__)
|
3
4
|
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
5
|
require 'software_challenge_client/version'
|
5
6
|
|
6
7
|
Gem::Specification.new do |spec|
|
7
|
-
spec.name =
|
8
|
+
spec.name = 'software_challenge_client'
|
8
9
|
spec.version = SoftwareChallengeClient::VERSION
|
9
10
|
spec.authors = File.readlines('AUTHORS').select { |l| l[' <'] }.map { |l| l.match(/^(.*) *</)[1] }
|
10
11
|
spec.email = File.readlines('AUTHORS').select { |l| l[' <'] }.map { |l| l.match(/<(.*)>/)[1] }
|
11
12
|
|
12
|
-
spec.summary = '
|
13
|
+
spec.summary = 'Provides functions to build a client for the coding competition Software-Challenge Germany.'
|
13
14
|
spec.description = ''
|
14
15
|
spec.homepage = 'http://www.software-challenge.de'
|
15
16
|
|
16
17
|
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
17
|
-
spec.bindir =
|
18
|
+
spec.bindir = 'exe'
|
18
19
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
19
|
-
spec.require_paths = [
|
20
|
+
spec.require_paths = ['lib']
|
20
21
|
|
21
|
-
spec.required_ruby_version = '>= 2.
|
22
|
-
spec.add_dependency 'typesafe_enum'
|
22
|
+
spec.required_ruby_version = '>= 2.5.5'
|
23
23
|
spec.add_dependency 'builder'
|
24
|
+
spec.add_dependency 'typesafe_enum'
|
24
25
|
|
25
26
|
spec.add_development_dependency 'bundler', '>= 1.10'
|
26
|
-
spec.add_development_dependency 'rake', '>= 10.0'
|
27
|
-
spec.add_development_dependency 'yard', '>= 0.8'
|
28
|
-
spec.add_development_dependency 'rspec'
|
29
27
|
spec.add_development_dependency 'fuubar'
|
30
|
-
spec.add_development_dependency 'rubocop'
|
31
|
-
spec.add_development_dependency 'rubocop-rspec'
|
32
28
|
spec.add_development_dependency 'guard'
|
33
29
|
spec.add_development_dependency 'guard-rspec'
|
34
30
|
spec.add_development_dependency 'guard-rubocop'
|
35
31
|
spec.add_development_dependency 'pry'
|
36
|
-
spec.add_development_dependency 'pry-rescue'
|
37
|
-
spec.add_development_dependency 'pry-coolline'
|
38
32
|
spec.add_development_dependency 'pry-byebug'
|
33
|
+
spec.add_development_dependency 'pry-coolline'
|
34
|
+
spec.add_development_dependency 'pry-rescue'
|
35
|
+
spec.add_development_dependency 'rake', '>= 10.0'
|
36
|
+
spec.add_development_dependency 'rspec'
|
37
|
+
spec.add_development_dependency 'rubocop'
|
38
|
+
spec.add_development_dependency 'rubocop-rspec'
|
39
|
+
spec.add_development_dependency 'solargraph'
|
40
|
+
spec.add_development_dependency 'yard', '>= 0.8'
|
39
41
|
end
|