rubyzip 0.5.12 → 0.9.1
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of rubyzip might be problematic. Click here for more details.
- data/ChangeLog +151 -17
- data/NEWS +13 -0
- data/README +2 -0
- data/Rakefile +2 -3
- data/TODO +2 -3
- data/lib/download_quizzes.rb +119 -0
- data/lib/quiz1/t/solutions/Bill Guindon/solitaire.rb +205 -0
- data/lib/quiz1/t/solutions/Carlos/solitaire.rb +111 -0
- data/lib/quiz1/t/solutions/Dennis Ranke/solitaire.rb +111 -0
- data/lib/quiz1/t/solutions/Florian Gross/solitaire.rb +301 -0
- data/lib/quiz1/t/solutions/Glen M. Lewis/solitaire.rb +268 -0
- data/lib/quiz1/t/solutions/James Edward Gray II/solitaire.rb +132 -0
- data/lib/quiz1/t/solutions/Jamis Buck/bin/main.rb +13 -0
- data/lib/quiz1/t/solutions/Jamis Buck/lib/cipher.rb +230 -0
- data/lib/quiz1/t/solutions/Jamis Buck/lib/cli.rb +24 -0
- data/lib/quiz1/t/solutions/Jamis Buck/test/tc_deck.rb +30 -0
- data/lib/quiz1/t/solutions/Jamis Buck/test/tc_key-stream.rb +19 -0
- data/lib/quiz1/t/solutions/Jamis Buck/test/tc_keying-algorithms.rb +31 -0
- data/lib/quiz1/t/solutions/Jamis Buck/test/tc_solitaire-cipher.rb +66 -0
- data/lib/quiz1/t/solutions/Jamis Buck/test/tc_unkeyed-algorithm.rb +17 -0
- data/lib/quiz1/t/solutions/Jamis Buck/test/tests.rb +2 -0
- data/lib/quiz1/t/solutions/Jim Menard/solitaire_cypher.rb +204 -0
- data/lib/quiz1/t/solutions/Jim Menard/test.rb +47 -0
- data/lib/quiz1/t/solutions/Moses Hohman/cipher.rb +97 -0
- data/lib/quiz1/t/solutions/Moses Hohman/deck.rb +140 -0
- data/lib/quiz1/t/solutions/Moses Hohman/solitaire.rb +14 -0
- data/lib/quiz1/t/solutions/Moses Hohman/test_cipher.rb +68 -0
- data/lib/quiz1/t/solutions/Moses Hohman/test_deck.rb +146 -0
- data/lib/quiz1/t/solutions/Moses Hohman/test_util.rb +38 -0
- data/lib/quiz1/t/solutions/Moses Hohman/testsuite.rb +5 -0
- data/lib/quiz1/t/solutions/Moses Hohman/util.rb +27 -0
- data/lib/quiz1/t/solutions/Niklas Frykholm/solitaire.rb +151 -0
- data/lib/quiz1/t/solutions/Thomas Leitner/solitaire.rb +198 -0
- data/lib/zip/ioextras.rb +17 -15
- data/lib/zip/zip.rb +394 -112
- data/lib/zip/zipfilesystem.rb +2 -2
- data/test/gentestfiles.rb +3 -1
- data/test/zipfilesystemtest.rb +2 -2
- data/test/ziptest.rb +34 -29
- metadata +35 -12
- data/samples/zipdialogui.rb +0 -80
- data/test/data/file2.txt.other +0 -0
- data/test/zlibtest.rb +0 -26
@@ -0,0 +1,111 @@
|
|
1
|
+
class Numeric
|
2
|
+
def value
|
3
|
+
self
|
4
|
+
end
|
5
|
+
|
6
|
+
def to_letter
|
7
|
+
((self-1)%26 + ?A).chr
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
class String
|
12
|
+
# returns an array with the code of the letters,
|
13
|
+
# padded with the code of X
|
14
|
+
def to_numbers
|
15
|
+
res=upcase.unpack("C*").collect { |b|
|
16
|
+
if b.between? ?A, ?Z
|
17
|
+
b - ?A + 1
|
18
|
+
else
|
19
|
+
nil
|
20
|
+
end
|
21
|
+
}.compact
|
22
|
+
# 24 == X
|
23
|
+
res.fill 24, res.length, (5 - res.length % 5) % 5
|
24
|
+
end
|
25
|
+
|
26
|
+
def crypt (deck, decrypt=false)
|
27
|
+
numbers = to_numbers
|
28
|
+
keystream = deck.generate_keystream numbers.length
|
29
|
+
result = ""
|
30
|
+
numbers.zip(keystream) do |n, k|
|
31
|
+
k = -k if decrypt
|
32
|
+
result << (n+k).to_letter
|
33
|
+
end
|
34
|
+
result
|
35
|
+
end
|
36
|
+
|
37
|
+
def encrypt (deck)
|
38
|
+
crypt deck, false
|
39
|
+
end
|
40
|
+
|
41
|
+
def decrypt (deck)
|
42
|
+
crypt deck, true
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
class Joker
|
47
|
+
def value
|
48
|
+
53
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
A = Joker.new
|
53
|
+
B = Joker.new
|
54
|
+
|
55
|
+
class Array
|
56
|
+
def wrap_down pos
|
57
|
+
pos %= length
|
58
|
+
if pos == 0
|
59
|
+
pos = length
|
60
|
+
end
|
61
|
+
pos
|
62
|
+
end
|
63
|
+
|
64
|
+
def next_key
|
65
|
+
# step 2: move A joker down 1 card
|
66
|
+
pos = index A
|
67
|
+
slice! pos
|
68
|
+
pos = wrap_down(pos + 1)
|
69
|
+
self[pos, 0] = A
|
70
|
+
|
71
|
+
# step 3: move B joker down 2 cards
|
72
|
+
pos = index B
|
73
|
+
slice! pos
|
74
|
+
pos = wrap_down(pos + 2)
|
75
|
+
self[pos, 0] = B
|
76
|
+
|
77
|
+
# step 4: triple cut
|
78
|
+
first_joker, second_joker = [index(A), index(B)].sort
|
79
|
+
cards_above = slice! 0...first_joker
|
80
|
+
second_joker -= cards_above.length
|
81
|
+
cards_below = slice! second_joker+1..-1
|
82
|
+
push *cards_above
|
83
|
+
unshift *cards_below
|
84
|
+
|
85
|
+
# step 5: count cut using the value of the bottom card.
|
86
|
+
# reinsert above the last card
|
87
|
+
cut = slice! 0, last.value
|
88
|
+
self[-1,0] = cut
|
89
|
+
|
90
|
+
# step 6: find the letter
|
91
|
+
card = self[first.value]
|
92
|
+
|
93
|
+
return Joker===card ? nil : card.value
|
94
|
+
end
|
95
|
+
|
96
|
+
def generate_keystream len
|
97
|
+
(1..len).collect {|i| next_key or redo }
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
def new_deck
|
102
|
+
(1..52).to_a + [A, B]
|
103
|
+
end
|
104
|
+
|
105
|
+
res = if ARGV[0] == "-d"
|
106
|
+
ARGV[1..-1].join("").decrypt(new_deck)
|
107
|
+
else
|
108
|
+
ARGV.join("").encrypt(new_deck)
|
109
|
+
end
|
110
|
+
|
111
|
+
puts res.scan(/.{5}/).join(" ")
|
@@ -0,0 +1,111 @@
|
|
1
|
+
class Deck
|
2
|
+
def initialize
|
3
|
+
@deck = Array.new(54) {|i| i}
|
4
|
+
end
|
5
|
+
|
6
|
+
def create_keystream(count)
|
7
|
+
stream = []
|
8
|
+
count.times do
|
9
|
+
letter = next_letter
|
10
|
+
redo unless letter
|
11
|
+
stream << letter
|
12
|
+
end
|
13
|
+
return stream
|
14
|
+
end
|
15
|
+
|
16
|
+
def next_letter
|
17
|
+
##
|
18
|
+
# move the jokers
|
19
|
+
##
|
20
|
+
|
21
|
+
2.times do |j|
|
22
|
+
# find the joker
|
23
|
+
index = @deck.index(52 + j)
|
24
|
+
|
25
|
+
# remove it from the deck
|
26
|
+
@deck.delete_at(index)
|
27
|
+
|
28
|
+
# calculate new index
|
29
|
+
index = ((index + j) % 53) + 1
|
30
|
+
|
31
|
+
# insert the joker at that index
|
32
|
+
@deck[index, 0] = 52 + j
|
33
|
+
end
|
34
|
+
|
35
|
+
##
|
36
|
+
# do the tripple cut
|
37
|
+
##
|
38
|
+
|
39
|
+
# first find both jokers
|
40
|
+
a = @deck.index(52)
|
41
|
+
b = @deck.index(53)
|
42
|
+
|
43
|
+
# sort the two indeces
|
44
|
+
low, hi = [a, b].sort
|
45
|
+
|
46
|
+
# get the lower and upper parts of the deck
|
47
|
+
upper = @deck.slice!((hi + 1)..-1)
|
48
|
+
lower = @deck.slice!(0, low)
|
49
|
+
|
50
|
+
# swap them
|
51
|
+
@deck = upper + @deck + lower
|
52
|
+
|
53
|
+
##
|
54
|
+
# do the count cut
|
55
|
+
##
|
56
|
+
|
57
|
+
# find out the number of cards to cut
|
58
|
+
count = value_at(53)
|
59
|
+
|
60
|
+
# remove them from the top of the deck
|
61
|
+
cards = @deck.slice!(0, count)
|
62
|
+
|
63
|
+
# reinsert them just above the lowest card
|
64
|
+
@deck[-1, 0] = cards
|
65
|
+
|
66
|
+
return letter_at(value_at(0))
|
67
|
+
end
|
68
|
+
|
69
|
+
def value_at(index)
|
70
|
+
id = @deck[index]
|
71
|
+
(id > 51) ? 53 : id + 1
|
72
|
+
end
|
73
|
+
|
74
|
+
def letter_at(index)
|
75
|
+
id = @deck[index]
|
76
|
+
(id > 51) ? nil : (id % 26) + 1
|
77
|
+
end
|
78
|
+
|
79
|
+
def to_s
|
80
|
+
@deck.map {|v| (v > 51) ? (v + 13).chr : (v + 1).to_s}.join(' ')
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def encode(data, keystream)
|
85
|
+
result = []
|
86
|
+
data.size.times {|i| result << ((data[i] + keystream[i]) % 26)}
|
87
|
+
return result
|
88
|
+
end
|
89
|
+
|
90
|
+
def decode(data, keystream)
|
91
|
+
encode(data, keystream.map {|v| 26 - v})
|
92
|
+
end
|
93
|
+
|
94
|
+
def data_to_string(data)
|
95
|
+
data = data.map {|v| (v + 65).chr}.join
|
96
|
+
(0...(data.size / 5)).map {|i| data[i * 5, 5]}.join(' ')
|
97
|
+
end
|
98
|
+
|
99
|
+
if ARGV.size != 1
|
100
|
+
puts "Usage: solitaire.rb MESSAGE"
|
101
|
+
exit
|
102
|
+
end
|
103
|
+
|
104
|
+
data = ARGV[0].upcase.split(//).select {|c| c =~ /[A-Z]/}.map {|c| c[0] - 65}
|
105
|
+
data += [?X - 65] * (4 - (data.size + 4) % 5)
|
106
|
+
|
107
|
+
deck = Deck.new
|
108
|
+
keystream = deck.create_keystream(data.size)
|
109
|
+
|
110
|
+
puts 'encoded:', data_to_string(encode(data, keystream))
|
111
|
+
puts 'decoded:', data_to_string(decode(data, keystream))
|
@@ -0,0 +1,301 @@
|
|
1
|
+
class Array
|
2
|
+
# Moves the item from a specified index to
|
3
|
+
# just before the item with the specified index.
|
4
|
+
def move(from_index, to_index)
|
5
|
+
from_index += self.size if from_index < 0
|
6
|
+
to_index += self.size if to_index < 0
|
7
|
+
|
8
|
+
item = self.slice!(from_index)
|
9
|
+
self.insert(to_index, item)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
module Solitaire
|
14
|
+
extend self
|
15
|
+
|
16
|
+
Letters = ('A' .. 'Z').to_a
|
17
|
+
|
18
|
+
class Card < Struct.new(:face, :type)
|
19
|
+
Faces = [:ace, :two, :three, :four, :five, :six, :seven,
|
20
|
+
:eight, :nine, :ten, :jack, :queen, :king]
|
21
|
+
Types = [:clubs, :diamonds, :hearts, :spades, :special]
|
22
|
+
SpecialFaces = [:joker_a, :joker_b]
|
23
|
+
|
24
|
+
def self.deck
|
25
|
+
Types.map do |type|
|
26
|
+
if type == :special
|
27
|
+
SpecialFaces.map do |face|
|
28
|
+
new(face, type)
|
29
|
+
end
|
30
|
+
else
|
31
|
+
Faces.map do |face|
|
32
|
+
new(face, type)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end.flatten
|
36
|
+
end
|
37
|
+
|
38
|
+
def special?; type == :special; end
|
39
|
+
|
40
|
+
def value
|
41
|
+
if special? then 53
|
42
|
+
else
|
43
|
+
Faces.index(face) + 1 + 13 * Types.index(type)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def letter
|
48
|
+
Letters[(value - 1) % 26]
|
49
|
+
end
|
50
|
+
|
51
|
+
def name
|
52
|
+
if face == :joker_a then "JokerA"
|
53
|
+
elsif face == :joker_b then "JokerB"
|
54
|
+
else
|
55
|
+
face_str = face.to_s.capitalize.gsub(/_(\w)/) { $1.upcase }
|
56
|
+
type_str = type.to_s.capitalize
|
57
|
+
face_str + " of " + type_str
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def compact_inspect
|
62
|
+
if face == :joker_a then "A"
|
63
|
+
elsif face == :joker_b then "B"
|
64
|
+
else value end
|
65
|
+
end
|
66
|
+
|
67
|
+
def inspect
|
68
|
+
"#<#{self.class} #{name} (#{letter}/#{value})>"
|
69
|
+
end
|
70
|
+
alias :to_s :inspect
|
71
|
+
|
72
|
+
deck.each do |card|
|
73
|
+
const_set(card.name.sub(" of ", "Of"), card)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
class KeyStream
|
78
|
+
def initialize(key_method = nil)
|
79
|
+
case key_method
|
80
|
+
when true then
|
81
|
+
@deck = Card.deck.sort_by { rand }
|
82
|
+
when String then
|
83
|
+
@deck = Card.deck
|
84
|
+
generate_letter(key_method)
|
85
|
+
else
|
86
|
+
@deck = Card.deck
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def generate_letter(seed_phrase = nil)
|
91
|
+
if seed_phrase
|
92
|
+
seed_phrase = Solitaire.clean(seed_phrase)
|
93
|
+
seed_phrase = nil if seed_phrase.empty?
|
94
|
+
end
|
95
|
+
|
96
|
+
result = nil
|
97
|
+
|
98
|
+
until result
|
99
|
+
deck_size = @deck.size
|
100
|
+
|
101
|
+
# Move JokerA down one card
|
102
|
+
old_a_pos = @deck.index(Card::JokerA)
|
103
|
+
new_a_pos = case old_a_pos
|
104
|
+
when deck_size - 1 then 1
|
105
|
+
else old_a_pos + 1
|
106
|
+
end
|
107
|
+
@deck.move(old_a_pos, new_a_pos)
|
108
|
+
|
109
|
+
# Move JokerB down two cards
|
110
|
+
old_b_pos = @deck.index(Card::JokerB)
|
111
|
+
new_b_pos = case old_b_pos
|
112
|
+
when deck_size - 1 then 2
|
113
|
+
when deck_size - 2 then 1
|
114
|
+
else old_b_pos + 2
|
115
|
+
end
|
116
|
+
@deck.move(old_b_pos, new_b_pos)
|
117
|
+
|
118
|
+
# Perform triple cut
|
119
|
+
top_pos, bot_pos = [@deck.index(Card::JokerA), @deck.index(Card::JokerB)].sort
|
120
|
+
@deck.replace(
|
121
|
+
@deck[(bot_pos + 1) .. -1] +
|
122
|
+
@deck[top_pos .. bot_pos] +
|
123
|
+
@deck[0 ... top_pos])
|
124
|
+
|
125
|
+
# Perform count cut
|
126
|
+
top = @deck.slice!(0 ... @deck.last.value)
|
127
|
+
@deck.insert(-2, *top)
|
128
|
+
|
129
|
+
if seed_phrase
|
130
|
+
key = seed_phrase.slice!(0, 1)
|
131
|
+
top = @deck.slice!(0 ... Solitaire.letter_to_number(key))
|
132
|
+
@deck.insert(-2, *top)
|
133
|
+
result = true if seed_phrase.empty?
|
134
|
+
else
|
135
|
+
# Fetch result
|
136
|
+
card = @deck[@deck.first.value]
|
137
|
+
result = card.letter unless card.special?
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
return result
|
142
|
+
end
|
143
|
+
alias :shift :generate_letter
|
144
|
+
end
|
145
|
+
|
146
|
+
def letter_to_number(letter)
|
147
|
+
Letters.index(letter) + 1
|
148
|
+
end
|
149
|
+
|
150
|
+
def number_to_letter(number)
|
151
|
+
Letters[number - 1]
|
152
|
+
end
|
153
|
+
|
154
|
+
def clean(text)
|
155
|
+
text.upcase.delete("^A-Z")
|
156
|
+
end
|
157
|
+
|
158
|
+
def pretty(text)
|
159
|
+
clean(text).scan(/.{1,5}/).join(" ")
|
160
|
+
end
|
161
|
+
|
162
|
+
def encrypt(raw_text, keystream = nil, pretty = true)
|
163
|
+
keystream ||= KeyStream.new
|
164
|
+
text = clean(raw_text)
|
165
|
+
text += "X" * ((text.size / 5.0).ceil * 5 - text.size)
|
166
|
+
|
167
|
+
result = ""
|
168
|
+
0.upto(text.size - 1) do |index|
|
169
|
+
source_num = letter_to_number(text[index, 1])
|
170
|
+
key_num = letter_to_number(keystream.shift)
|
171
|
+
result << number_to_letter((source_num + key_num) % 26)
|
172
|
+
end
|
173
|
+
|
174
|
+
result = pretty(result) if pretty
|
175
|
+
return result
|
176
|
+
end
|
177
|
+
|
178
|
+
def decrypt(raw_text, keystream = nil, pretty = true)
|
179
|
+
keystream ||= KeyStream.new
|
180
|
+
text = clean(raw_text)
|
181
|
+
|
182
|
+
result = ""
|
183
|
+
0.upto(text.size - 1) do |index|
|
184
|
+
source_num = letter_to_number(text[index, 1])
|
185
|
+
key_num = letter_to_number(keystream.shift)
|
186
|
+
result << number_to_letter((source_num - key_num) % 26)
|
187
|
+
end
|
188
|
+
|
189
|
+
result = pretty(result) if pretty
|
190
|
+
return result
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
if __FILE__ == $0
|
195
|
+
require 'optparse'
|
196
|
+
|
197
|
+
options = {
|
198
|
+
:mode => nil,
|
199
|
+
:keystream => nil,
|
200
|
+
:keylength => 80,
|
201
|
+
:text => nil
|
202
|
+
}
|
203
|
+
|
204
|
+
ARGV.options do |opts|
|
205
|
+
script_name = File.basename($0)
|
206
|
+
opts.banner = "Usage: ruby #{script_name} [options]"
|
207
|
+
|
208
|
+
opts.separator ""
|
209
|
+
|
210
|
+
opts.on("-d", "--decrypt",
|
211
|
+
"Decrypt an encrypted message.",
|
212
|
+
"This is the default if the message looks encrypted.") do
|
213
|
+
options[:mode] = :decrypt
|
214
|
+
end
|
215
|
+
opts.on("-e", "--encrypt",
|
216
|
+
"Encrypt an unencrypted message.") do
|
217
|
+
options[:mode] = :encrypt
|
218
|
+
end
|
219
|
+
opts.on("-m", "--message message",
|
220
|
+
"Specify the message.",
|
221
|
+
"Default: Read from terminal.") do |text|
|
222
|
+
options[:text] = text
|
223
|
+
end
|
224
|
+
opts.on("-k", "--key=key",
|
225
|
+
"Specify the key that will be used for shuffling the deck.",
|
226
|
+
"Default: Use an unshuffled deck.") do |key|
|
227
|
+
options[:keystream] = Solitaire::KeyStream.new(key)
|
228
|
+
end
|
229
|
+
opts.on("-R", "--random-key length", Integer,
|
230
|
+
"Use a randomly generated key for shuffling the deck.",
|
231
|
+
"The key length can be specified. It defaults to 80.",
|
232
|
+
"The key will be printed to the first line of STDOUT.") do |width|
|
233
|
+
options[:keylength] = width if width
|
234
|
+
options[:keystream] = :random
|
235
|
+
end
|
236
|
+
opts.on("-W", "--word-key file",
|
237
|
+
"Use a randomly generated key phrase.",
|
238
|
+
"It will consist of random words in the specified file.",
|
239
|
+
"The key length can be specified via the -R option.",
|
240
|
+
"The key phrase and the key will be printed to STDOUT.") do |word_file|
|
241
|
+
options[:keystream] = :random_words
|
242
|
+
options[:word_file] = word_file
|
243
|
+
end
|
244
|
+
|
245
|
+
opts.separator ""
|
246
|
+
|
247
|
+
opts.on("-h", "--help",
|
248
|
+
"Show this help message.") do
|
249
|
+
puts opts; exit
|
250
|
+
end
|
251
|
+
|
252
|
+
opts.parse!
|
253
|
+
end
|
254
|
+
|
255
|
+
input = options[:text] || STDIN.read
|
256
|
+
|
257
|
+
options[:mode] = :decrypt if /\A(?:[A-Z]{5}\s*)+\Z/.match(input)
|
258
|
+
|
259
|
+
case options[:keystream]
|
260
|
+
when :random then
|
261
|
+
key = Array.new(options[:keylength]) { Solitaire::Letters[rand(26)] }.join
|
262
|
+
|
263
|
+
puts "Key: " + Solitaire.pretty(key)
|
264
|
+
options[:keystream] = Solitaire::KeyStream.new(key)
|
265
|
+
when :random_words then
|
266
|
+
begin
|
267
|
+
words = File.read(options[:word_file]).scan(/\w+/)
|
268
|
+
rescue
|
269
|
+
STDERR.puts "Word file doesn't exist or can't be read."
|
270
|
+
exit -1
|
271
|
+
end
|
272
|
+
|
273
|
+
words_size = words.size
|
274
|
+
|
275
|
+
min_words = options[:keylength] / 6
|
276
|
+
if words_size < min_words
|
277
|
+
STDERR.puts "Word file must contain at least #{min_words} words," +
|
278
|
+
" but it contains only #{words_size} words!"
|
279
|
+
exit -2
|
280
|
+
end
|
281
|
+
|
282
|
+
key = []
|
283
|
+
until key.join("").length >= options[:keylength]
|
284
|
+
key << words[rand(words_size)]
|
285
|
+
end
|
286
|
+
key = key.join(" ")
|
287
|
+
|
288
|
+
puts "Keyphrase: " + key
|
289
|
+
puts "Key: " + Solitaire.pretty(key)
|
290
|
+
options[:keystream] = Solitaire::KeyStream.new(key)
|
291
|
+
end
|
292
|
+
|
293
|
+
if options[:mode] == :decrypt
|
294
|
+
puts Solitaire.decrypt(input, options[:keystream])
|
295
|
+
else
|
296
|
+
unless options[:keystream]
|
297
|
+
STDERR.puts "WARNING: Using an unshuffled deck for encrypting!"
|
298
|
+
end
|
299
|
+
puts Solitaire.encrypt(input, options[:keystream])
|
300
|
+
end
|
301
|
+
end
|