[QUIZ] The Solitaire Cipher (#1)

R

Ruby Quiz

The three rules of Ruby Quiz:

1. Please do not post any solutions or spoiler discussion for this quiz until
48 hours have passed from the time on this message.

2. Support Ruby Quiz by submitting ideas as often as you can:

http://www.grayproductions.net/ruby_quiz/

3. Enjoy!

-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=

Cryptologist Bruce Schneier designed the hand cipher "Solitaire" for Neal
Stephenson's book "Cryptonomicon". Created to be the first truly secure hand
cipher, Solitaire requires only a deck of cards for the encryption and
decryption of messages.

While it's true that Solitaire is easily completed by hand given ample time,
using a computer is much quicker and easier. Because of that, Solitaire
conversion routines are available in many languages, though I've not yet run
across one in Ruby.

This week's quiz is to write a Ruby script that does the encryption and
decryption of messages using the Solitaire cipher.

Let's look at the steps of encrypting a message with Solitaire.

1. Discard any non A to Z characters, and uppercase all remaining letters.
Split the message into five character groups, using Xs to pad the last group, if
needed. If we begin with the message "Code in Ruby, live longer!", for example,
we would now have:

CODEI NRUBY LIVEL ONGER

2. Use Solitaire to generate a keystream letter for each letter in the message.
This step is detailed below, but for the sake of example let's just say we get:

DWJXH YRFDG TMSHP UURXJ

3. Convert the message from step 1 into numbers, A = 1, B = 2, etc:

3 15 4 5 9 14 18 21 2 25 12 9 22 5 12 15 14 7 5 18

4. Convert the keystream letters from step 2 using the same method:

4 23 10 24 8 25 18 6 4 7 20 13 19 8 16 21 21 18 24 10

5. Add the message numbers from step 3 to the keystream numbers from step 4 and
subtract 26 from the result if it is greater than 26. For example, 6 + 10 = 16
as expected, but 26 + 1 = 1 (27 - 26):

7 12 14 3 17 13 10 1 6 6 6 22 15 13 2 10 9 25 3 2

6. Convert the numbers from step 5 back to letters:

GLNCQ MJAFF FVOMB JIYCB

That took a while to break down, but it's really a very simple process.
Decrypting with Solitaire is even easier, so let's look at those steps now.
We'll work backwards with our example now, decrypting "GLNCQ MJAFF FVOMB JIYCB".

1. Use Solitaire to generate a keystream letter for each letter in the message
to be decoded. Again, I detail this process below, but sender and receiver use
the same key and will get the same letters:

DWJXH YRFDG TMSHP UURXJ

2. Convert the message to be decoded to numbers:

7 12 14 3 17 13 10 1 6 6 6 22 15 13 2 10 9 25 3 2

3. Convert the keystream letters from step 1 to numbers:

4 23 10 24 8 25 18 6 4 7 20 13 19 8 16 21 21 18 24 10

4. Subtract the keystream numbers from step 3 from the message numbers from
step 2. If the keystream number is less than or equal to the message number,
add 26 to the keystream number before subtracting. For example, 22 - 1 = 21 as
expected, but 1 - 22 = 5 (27 - 22):

3 15 4 5 9 14 18 21 2 25 12 9 22 5 12 15 14 7 5 18

5. Convert the numbers from step 4 back to letters:

CODEI NRUBY LIVEL ONGER

Transforming messages is that simple. Finally, let's look at the missing piece
of the puzzle, generating the keystream letters.

First, let's talk a little about the deck of cards. Solitaire needs a full deck
of 52 cards and the two jokers. The jokers need to be visually distinct and
I'll refer to them below as A and B. Some steps involve assigning a value to
the cards. In those cases, use the cards face value as a base, Ace = 1, 2 =
2... 10 = 10, Jack = 11, Queen = 12, King = 13. Then modify the base by the
bridge ordering of suits, Clubs is simply the base value, Diamonds is base value
+ 13, Hearts is base value + 26, and Spades is base value + 39. Either joker
values at 53. When the cards must represent a letter Clubs and Diamonds values
are taken to be the number of the letter (1 to 26), as are Hearts and Spades
after subtracting 26 from their value (27 to 52 drops to 1 to 26). Now let's
make sense of all that by putting it to use.

1. Key the deck. This is the critical step in the actual operation of the
cipher and the heart of it's security. There are many methods to go about this,
such as shuffling a deck and then arranging the receiving deck in the same order
or tracking a bridge column in the paper and using that to order the cards.
Because we want to be able to test our answers though, we'll use an unkeyed
deck, cards in order of value. That is, from top to bottom, we'll always start
with the deck:

Ace of Clubs
...to...
King of Clubs
Ace of Diamonds
...to...
King of Diamonds
Ace of Hearts
...to...
King of Hearts
Ace of Spades
...to...
King of Spades
"A" Joker
"B" Joker

2. Move the A joker down one card. If the joker is at the bottom of the deck,
move it to just below the first card. (Consider the deck to be circular.) The
first time we do this, the deck will go from:

1 2 3 ... 52 A B

To:

1 2 3 ... 52 B A

3. Move the B joker down two cards. If the joker is the bottom card, move it
just below the second card. If the joker is the just above the bottom card,
move it below the top card. (Again, consider the deck to be circular.) This
changes our example deck to:

1 B 2 3 4 ... 52 A

4. Perform a triple cut around the two jokers. All cards above the top joker
move to below the bottom joker and vice versa. The jokers and the cards between
them do not move. This gives us:

B 2 3 4 ... 52 A 1

5. Perform a count cut using the value of the bottom card. Cut the bottom
card's value in cards off the top of the deck and reinsert them just above the
bottom card. This changes our deck to:

2 3 4 ... 52 A B 1 (the 1 tells us to move just the B)

6. Find the output letter. Convert the top card to it's value and count down
that many cards from the top of the deck, with the top card itself being card
number one. Look at the card immediately after your count and convert it to a
letter. This is the next letter in the keystream. If the output card is a
joker, no letter is generated this sequence. This step does not alter the deck.
For our example, the output letter is:

D (the 2 tells us to count down to the 4, which is a D)

7. Return to step 2, if more letters are needed.

For the sake of testing, the first ten output letters for an unkeyed deck are:

D (4) W (49) J (10) Skip Joker (53) X (24) H (8)
Y (51) R (44) F (6) D (4) G (33)

That's all there is to Solitaire, finally. It's really longer to explain than
it is to code up.

Solutions to this quiz should accept a message as a command line argument and
encrypt or decrypt is as needed. It should be easy to tell which is needed by
the pattern of the message, but you can use a switch if you prefer.

All the examples for this quiz assume an unkeyed deck, but your script can
provide a way to key the deck, if desired. (A real script would require this,
of course.)

Here's a couple of messages to test your work with. You'll know when you have
them right:

CLEPK HHNIY CFPWH FDFEH

ABVAW LWZSY OORYK DUPVH
 
J

Jamis Buck

Ruby said:
5. Perform a count cut using the value of the bottom card. Cut the bottom
card's value in cards off the top of the deck and reinsert them just above the
bottom card. This changes our deck to:

2 3 4 ... 52 A B 1 (the 1 tells us to move just the B)

6. Find the output letter. Convert the top card to it's value and count down
that many cards from the top of the deck, with the top card itself being card
number one. Look at the card immediately after your count and convert it to a
letter. This is the next letter in the keystream. If the output card is a
joker, no letter is generated this sequence. This step does not alter the deck.
For our example, the output letter is:

D (the 2 tells us to count down to the 4, which is a D)

Step six says that the "top card itself [is] card number one". That
said, if I count 2 from the top, with the top card being #1, shouldn't
that have given me a 3 (per the listing in step 5), instead of a 4?
Which means the first output letter would be "C"...

Am I missing something? Did I misread the instructions, or is there an
error there?

- Jamis

--
Jamis Buck
(e-mail address removed)
http://www.jamisbuck.org/jamis

"I use octal until I get to 8, and then I switch to decimal."
 
J

James Edward Gray II

Ruby said:
6. Find the output letter. Convert the top card to it's value and
count down
that many cards from the top of the deck, with the top card itself
being card
number one. Look at the card immediately after your count and
convert it to a
letter. This is the next letter in the keystream. If the output
card is a
joker, no letter is generated this sequence. This step does not
alter the deck.
For our example, the output letter is:
D (the 2 tells us to count down to the 4, which is a D)

Step six says that the "top card itself [is] card number one". That
said, if I count 2 from the top, with the top card being #1, shouldn't
that have given me a 3 (per the listing in step 5), instead of a 4?
Which means the first output letter would be "C"...

Am I missing something? Did I misread the instructions, or is there an
error there?

The top card is number one, yes, and you do count down two cards. The
instructions for step 6 say, immediately after that, "Look at the card
immediately after your count and...". So card 2 is number 1, card 3 is
number 2 and the card immediately after that count is card 4.

I apologize if it isn't clear. I was disappointed with how long and
complex I made that quiz look, it's actually a super simple procedure.

James Edward Gray II
 
J

James Edward Gray II

Thanks for the clarification, James.

My pleasure.

If anyone ever has trouble reading my quiz babble, don't hesitate to
ask. I'll try to watch for clarification requests.
This is great fun. :) I've actually got it working!

I'm glad you enjoyed it. That's the idea.
(I did it the hard way, though, using Copland...I figured it might be
another good way to demonstrate how to use IoC, even though the
assignment is really much too small to use IoC efficiently...)

Well, since I'm 100% Copland ignorant, I look forward to seeing your
solution. Don't forget to post it after the No-Spoiler Period expires.

James Edward Gray II
 
D

Dominik Werder

So do I understand that right, Bruce Schneier claims that Solitair is a
real cyrptographic secure pseudo random number generator?
Cool, a PRNG for the small budget :)
 
J

James Edward Gray II

So do I understand that right, Bruce Schneier claims that Solitair is
a real cyrptographic secure pseudo random number generator?
Cool, a PRNG for the small budget :)

The real question is, do you think he's right? ;)

James Edward Gray II
 
B

Bill Guindon

1. Please do not post any solutions or spoiler discussion for this quiz until
48 hours have passed from the time on this message.

Maybe a fixed cutoff would be best... nothing before Sunday at **pm
(GMT or UTC) no matter when it's posted.
5. Convert the numbers from step 4 back to letters:

CODEI NRUBY LIVEL ONGER
Solutions to this quiz should accept a message as a command line argument and
encrypt or decrypt is as needed. It should be easy to tell which is needed by
the pattern of the message, but you can use a switch if you prefer.

What if you fed it the output from above, exactly as is? Either
you're assuming that "normal" text is fed into it, or you have
seriously overestimated my regex abilities ;)

An amusing little puzzle. You're off to a great start :)
 
J

James Edward Gray II

Maybe a fixed cutoff would be best... nothing before Sunday at **pm
(GMT or UTC) no matter when it's posted.

One of the early quizzes for the Perl Quiz of the Week was to take an
e-mail on STDIN and determine if it was okay to send spoilers yet. Do
we need to do that one too? ;)

I believe the assumption is that eventually I'll be a day or two late.
When that happens the No-Spoiler Period will still function as intended
the way I have it now.

I actually changed the period to 48 hours to make it super easy to
calculate at a glance. Just read the Date: field and add two days.

Also, discussion is not a problem. Just hold off on the spoilers,
hints and solutions until the 48 hours pass. Everything I've seen so
far has been fine.
What if you fed it the output from above, exactly as is? Either
you're assuming that "normal" text is fed into it, or you have
seriously overestimated my regex abilities ;)

I'm a reasonable interface kind of guy.

When I'm typing in a message to be encoded, I don't want to think in
all caps and five character increments, so I won't. When I'm decoding
a message, I know nothing about that string of letters and thus will
enter it as is (probably with copy and paste).

Knowing that, my program has the perfect clue to tell what I want,
without me prompting it. I like my software to read my mind like this.
It makes me feel loved. :) If this bothers you, or you already feel
loved enough, feel free to use a switch. I won't mind.

This was just a tidbit I threw in for fun. It's not the heart of the
quiz, obviously.
An amusing little puzzle. You're off to a great start :)

Thank you. I really appreciate that. I enjoy little code challenges
and I hope others are enjoying them too.

Tune in next week... :D

James Edward Gray II
 
F

Florian Gross

Moin!

Here's my solution for the Solitaire Cipher quiz. It's fairly
class-oriented and longish.

There's support for generating either a completely random key or one
that's based on a word file.

I had to fight a nasty bug in my KeyStream generator while writing this
(I screwed up the triple cut) -- I think it would have been easier to
solve if I had developed test-first.

This was a nice quiz and I eagerly look forward to the next one. Thank
you. :)

Regards,
Florian Gross


class Array
# Moves the item from a specified index to
# just before the item with the specified index.
def move(from_index, to_index)
from_index += self.size if from_index < 0
to_index += self.size if to_index < 0

item = self.slice!(from_index)
self.insert(to_index, item)
end
end

module Solitaire
extend self

Letters = ('A' .. 'Z').to_a

class Card < Struct.new:)face, :type)
Faces = [:ace, :two, :three, :four, :five, :six, :seven,
:eight, :nine, :ten, :jack, :queen, :king]
Types = [:clubs, :diamonds, :hearts, :spades, :special]
SpecialFaces = [:joker_a, :joker_b]

def self.deck
Types.map do |type|
if type == :special
SpecialFaces.map do |face|
new(face, type)
end
else
Faces.map do |face|
new(face, type)
end
end
end.flatten
end

def special?; type == :special; end

def value
if special? then 53
else
Faces.index(face) + 1 + 13 * Types.index(type)
end
end

def letter
Letters[(value - 1) % 26]
end

def name
if face == :joker_a then "JokerA"
elsif face == :joker_b then "JokerB"
else
face_str = face.to_s.capitalize.gsub(/_(\w)/) { $1.upcase }
type_str = type.to_s.capitalize
face_str + " of " + type_str
end
end

def compact_inspect
if face == :joker_a then "A"
elsif face == :joker_b then "B"
else value end
end

def inspect
"#<#{self.class} #{name} (#{letter}/#{value})>"
end
alias :to_s :inspect

deck.each do |card|
const_set(card.name.sub(" of ", "Of"), card)
end
end

class KeyStream
def initialize(key_method = nil)
case key_method
when true then
@deck = Card.deck.sort_by { rand }
when String then
@deck = Card.deck
generate_letter(key_method)
else
@deck = Card.deck
end
end

def generate_letter(seed_phrase = nil)
if seed_phrase
seed_phrase = Solitaire.clean(seed_phrase)
seed_phrase = nil if seed_phrase.empty?
end

result = nil

until result
deck_size = @deck.size

# Move JokerA down one card
old_a_pos = @deck.index(Card::JokerA)
new_a_pos = case old_a_pos
when deck_size - 1 then 1
else old_a_pos + 1
end
@deck.move(old_a_pos, new_a_pos)

# Move JokerB down two cards
old_b_pos = @deck.index(Card::JokerB)
new_b_pos = case old_b_pos
when deck_size - 1 then 2
when deck_size - 2 then 1
else old_b_pos + 2
end
@deck.move(old_b_pos, new_b_pos)

# Perform triple cut
top_pos, bot_pos = [@deck.index(Card::JokerA), @deck.index(Card::JokerB)].sort
@deck.replace(
@deck[(bot_pos + 1) .. -1] +
@deck[top_pos .. bot_pos] +
@deck[0 ... top_pos])

# Perform count cut
top = @deck.slice!(0 ... @deck.last.value)
@deck.insert(-2, *top)

if seed_phrase
key = seed_phrase.slice!(0, 1)
top = @deck.slice!(0 ... Solitaire.letter_to_number(key))
@deck.insert(-2, *top)
result = true if seed_phrase.empty?
else
# Fetch result
card = @deck[@deck.first.value]
result = card.letter unless card.special?
end
end

return result
end
alias :shift :generate_letter
end

def letter_to_number(letter)
Letters.index(letter) + 1
end

def number_to_letter(number)
Letters[number - 1]
end

def clean(text)
text.upcase.delete("^A-Z")
end

def pretty(text)
clean(text).scan(/.{1,5}/).join(" ")
end

def encrypt(raw_text, keystream = nil, pretty = true)
keystream ||= KeyStream.new
text = clean(raw_text)
text += "X" * ((text.size / 5.0).ceil * 5 - text.size)

result = ""
0.upto(text.size - 1) do |index|
source_num = letter_to_number(text[index, 1])
key_num = letter_to_number(keystream.shift)
result << number_to_letter((source_num + key_num) % 26)
end

result = pretty(result) if pretty
return result
end

def decrypt(raw_text, keystream = nil, pretty = true)
keystream ||= KeyStream.new
text = clean(raw_text)

result = ""
0.upto(text.size - 1) do |index|
source_num = letter_to_number(text[index, 1])
key_num = letter_to_number(keystream.shift)
result << number_to_letter((source_num - key_num) % 26)
end

result = pretty(result) if pretty
return result
end
end

if __FILE__ == $0
require 'optparse'

options = {
:mode => nil,
:keystream => nil,
:keylength => 80,
:text => nil
}

ARGV.options do |opts|
script_name = File.basename($0)
opts.banner = "Usage: ruby #{script_name} [options]"

opts.separator ""

opts.on("-d", "--decrypt",
"Decrypt an encrypted message.",
"This is the default if the message looks encrypted.") do
options[:mode] = :decrypt
end
opts.on("-e", "--encrypt",
"Encrypt an unencrypted message.") do
options[:mode] = :encrypt
end
opts.on("-m", "--message message",
"Specify the message.",
"Default: Read from terminal.") do |text|
options[:text] = text
end
opts.on("-k", "--key=key",
"Specify the key that will be used for shuffling the deck.",
"Default: Use an unshuffled deck.") do |key|
options[:keystream] = Solitaire::KeyStream.new(key)
end
opts.on("-R", "--random-key length", Integer,
"Use a randomly generated key for shuffling the deck.",
"The key length can be specified. It defaults to 80.",
"The key will be printed to the first line of STDOUT.") do |width|
options[:keylength] = width if width
options[:keystream] = :random
end
opts.on("-W", "--word-key file",
"Use a randomly generated key phrase.",
"It will consist of random words in the specified file.",
"The key length can be specified via the -R option.",
"The key phrase and the key will be printed to STDOUT.") do |word_file|
options[:keystream] = :random_words
options[:word_file] = word_file
end

opts.separator ""

opts.on("-h", "--help",
"Show this help message.") do
puts opts; exit
end

opts.parse!
end

input = options[:text] || STDIN.read

options[:mode] = :decrypt if /\A(?:[A-Z]{5}\s*)+\Z/.match(input)

case options[:keystream]
when :random then
key = Array.new(options[:keylength]) { Solitaire::Letters[rand(26)] }.join

puts "Key: " + Solitaire.pretty(key)
options[:keystream] = Solitaire::KeyStream.new(key)
when :random_words then
begin
words = File.read(options[:word_file]).scan(/\w+/)
rescue
STDERR.puts "Word file doesn't exist or can't be read."
exit -1
end

words_size = words.size

min_words = options[:keylength] / 6
if words_size < min_words
STDERR.puts "Word file must contain at least #{min_words} words," +
" but it contains only #{words_size} words!"
exit -2
end

key = []
until key.join("").length >= options[:keylength]
key << words[rand(words_size)]
end
key = key.join(" ")

puts "Keyphrase: " + key
puts "Key: " + Solitaire.pretty(key)
options[:keystream] = Solitaire::KeyStream.new(key)
end

if options[:mode] == :decrypt
puts Solitaire.decrypt(input, options[:keystream])
else
unless options[:keystream]
STDERR.puts "WARNING: Using an unshuffled deck for encrypting!"
end
puts Solitaire.encrypt(input, options[:keystream])
end
end
 
C

Carlos

Here's my solution for the Solitaire Cipher quiz. It's fairly
class-oriented and longish.

There's support for generating either a completely random key or one
that's based on a word file.

Well, here is mine. It's not class oriented, doesn't allow to key the deck
and doesn't guess anything :).

I discarded all these "card" notions and used only numbers (and jokers) :).

I liked this quiz very much.

The code:

class Numeric
def value
self
end

def to_letter
((self-1)%26 + ?A).chr
end
end

class String
# returns an array with the code of the letters,
# padded with the code of X
def to_numbers
res=upcase.unpack("C*").collect { |b|
if b.between? ?A, ?Z
b - ?A + 1
else
nil
end
}.compact
# 24 == X
res.fill 24, res.length, (5 - res.length % 5) % 5
end

def crypt (deck, decrypt=false)
numbers = to_numbers
keystream = deck.generate_keystream numbers.length
result = ""
numbers.zip(keystream) do |n, k|
k = -k if decrypt
result << (n+k).to_letter
end
result
end

def encrypt (deck)
crypt deck, false
end

def decrypt (deck)
crypt deck, true
end
end

class Joker
def value
53
end
end

A = Joker.new
B = Joker.new

class Array
def wrap_down pos
pos %= length
if pos == 0
pos = length
end
pos
end

def next_key
# step 2: move A joker down 1 card
pos = index A
slice! pos
pos = wrap_down(pos + 1)
self[pos, 0] = A

# step 3: move B joker down 2 cards
pos = index B
slice! pos
pos = wrap_down(pos + 2)
self[pos, 0] = B

# step 4: triple cut
first_joker, second_joker = [index(A), index(B)].sort
cards_above = slice! 0...first_joker
second_joker -= cards_above.length
cards_below = slice! second_joker+1..-1
push *cards_above
unshift *cards_below

# step 5: count cut using the value of the bottom card.
# reinsert above the last card
cut = slice! 0, last.value
self[-1,0] = cut

# step 6: find the letter
card = self[first.value]

return Joker===card ? nil : card.value
end

def generate_keystream len
(1..len).collect {|i| next_key or redo }
end
end

def new_deck
(1..52).to_a + [A, B]
end

res = if ARGV[0] == "-d"
ARGV[1..-1].join("").decrypt(new_deck)
else
ARGV.join("").encrypt(new_deck)
end

puts res.scan(/.{5}/).join(" ")
 
B

Bill Guindon

Moin!

Here's my solution for the Solitaire Cipher quiz. It's fairly
class-oriented and longish.

and another one gets added to the pile... also class oriented, just
under 200 lines with comments.

# bail if you have nothing to do.
unless ARGV[0]
print <<-STOP_PRINT
Encrypts/decrypts a string of text.
Usage: Give it some text!
STOP_PRINT
exit
end

# the obligatory deck of cards.
###############################################################################
class Deck
attr_reader :cards, :eek:rder

def initialize
@cards = []
@order = []
build_deck
end

def to_s
return @cards.to_s
end

def build_deck
[:Clubs, :Diamonds, :Hearts, :Spades].each do |suit|
rank = 0
'A23456789TJQK'.each_byte do |name|
# 'real' cards have a rank value
rank += 1
add_card(name.chr, suit, rank)
end
end

# Jokers have no rank value
'AB'.each_byte {|name| add_card(name.chr, :Joker, 0)}
end

# build order while adding.
def add_card(name, suit, rank)
card = Card.new(name, suit, rank)
@cards << card
@order << card.to_s
end

# Uses order to hunt for cards (Joker searches).
def find_card(name, suit)
return @order.index(Card.to_s(name, suit))
end

# does as many cuts as you give it.
def cut_cards(cuts)
cards = []
loc = 0
[cuts].flatten.each_with_index do |cut, idx|
cards[idx] = @cards[loc...cut]
loc = cut
end
cards << @cards[[email protected]]
end

def cards=(cards)
# flatten to handle cut results.
@cards = cards.flatten
# rebuild @order each time the deck changes.
update_order
end

# simple, but not very efficient.
def update_order
@order = @cards.collect {|card| card.to_s}
end

end

# the above deck is made up of...
###############################################################################
class Card
@@SUITS = {
:Clubs => 0,
:Diamonds => 13,
:Hearts => 26,
:Spades => 39,
:Joker => 53
}

def self.to_s(name, suit)
return name + ' ' + suit.to_s + "\n"
end

attr_reader :name, :suit, :rank

def initialize(name, suit, rank)
@name = name
@suit = suit
@rank = rank + @@SUITS[suit]
end

def to_s
Card.to_s(@name, @suit)
end
end

###############################################################################
class Solitaire

attr_reader :deck

def initialize(text)
@deck = Deck.new
@text = text.to_s
end

def process
# does it look encrypted? 5 letter blocks all uppercase?
looks_encrypted = @text.gsub(/[A-Z]{5}\s?/, '').empty?
results = ''

# prep the text for parsing.
if looks_encrypted
# strip off the blanks for consistency
text = @text.gsub(/\s/, '')
else
# Discard any non A to Z characters, and uppercase all remaining
text = @text.upcase.gsub!(/[^A-Z]/, '')
# Split the message into five character groups,
words, padding = word_count(text, 5)
# using Xs to pad the last group
text += padding
end

# parse it, and build up results.
text.each_byte do |char|
if looks_encrypted
char -= next_key
char += 26 if char < 65
else
char += next_key
char -= 26 if char > 90
end
results += char.chr
end

return space_text(results, 5)
end

# counts words as 5 char blocks
def word_count(text, len)
words, strays = text.length.divmod(len)
words += 1 if strays > 0
pad = "X" * (len - strays)
return [words, pad]
end

def space_text(text, len)
# adds a space every 5 letters.
# not sure how efficient this is.
return text.unpack(('A' + len.to_s) * word_count(text, len)[0]).join(' ')
end

def shift_card(name, suit, count)
# find the card
idx = @deck.find_card(name, suit)
# remove it from the deck.
card = @deck.cards.slice!(idx)
# calculate new placement.
idx += count
# the slice above makes length 'look' zero-based
idx -= @deck.cards.length if idx > @deck.cards.length

# glue the deck together as cards before, card, cards after.
@deck.cards = @deck.cards[0...idx] + [card] +
@deck.cards[[email protected]]
end

def next_key
shift_card('A', :Joker, 1)
shift_card('B', :Joker, 2)

# find the 2 jokers, and sort them for the cut.
jokers = [@deck.find_card('A', :Joker), @deck.find_card('B', :Joker)].sort
# increment the 2nd joker pos -- cut uses 'up to, but not including'
jokers[1] += 1
# reverse works nicely for the triple cut.
@deck.cards = @deck.cut_cards(jokers).reverse

# get the value from the last card, and cut up to it.
cuts = @deck.cut_cards([@deck.cards.last.rank, @deck.cards.length - 1])
@deck.cards = cuts[1] + cuts[0] + cuts[2]

# read top card value, count down that many cards + 1
key = @deck.cards[@deck.cards[0].rank].rank
# convert it to a letter, adjust if needed.
key -= 26 if key > 26

# if key is still > 26, then it's a joker!
return (key) unless key > 26
# try again if it's a joker!
next_key
end
end

test = Solitaire.new(ARGV[0])
puts test.process
puts test.deck
 
J

James Edward Gray II

(Not sure how best to post my solution, so I'm just replying to
Florian's message...figured it might be nice to keep the solutions all
in the same thread, at least...)

Sounds good to me.

My own solution is below. Eating my own dog food, as it were.

I went for a nice boring step by step translation into Ruby. No
classes. I too just treated the deck as an array of numbers and the
two jokers. I didn't provide a way to key the deck. Pretty boring
stuff here.

James Edward Gray II

#!/usr/bin/ruby -w

$deck = (1..52).to_a + ["A", "B"] # Unkeyed deck - Keystream Step 1

def encrypt(message)
# Step 1
message = message.upcase.tr("^A-Z", "")
i = 5
while i < message.size
message[i, 0] = " "
i += 6
end
message += "X" while message.rindex(" ") != message.size - 6

# Step 2
key_stream = generate(message.count("^ "))

# Step 3
values = message.split("").map { |letter| letter[0] - ?A + 1 }

# Step 4
key_values = key_stream.split("").map { |letter| letter[0] - ?A + 1 }

# Step 5
values.each_with_index do |value, index|
next if value < 0
values[index] = value + key_values[index]
values[index] -= 26 if values[index] > 26
end

# Step 6
message = (values.map { |number| (number - 1 + ?A).chr }).join("")

return message
end

def decrypt(message)
# Step 1
key_stream = generate(message.size)

# Step 2
values = message.split("").map { |letter| letter[0] - ?A + 1 }

# Step 3
key_values = key_stream.split("").map { |letter| letter[0] - ?A + 1 }

# Step 4
values.each_with_index do |value, index|
next if value < 0
if value <= key_values[index]
values[index] = value + 26 - key_values[index]
else
values[index] = value - key_values[index]
end
end

# Step 5
message = (values.map { |number| (number - 1 + ?A).chr }).join("")

return message
end

def generate(count) # Keystream Steps
key_stream = [ ]

until key_stream.size == count
# Step 2
a = $deck.index("A")
if a == 53
$deck.insert(1, $deck.pop)
else
$deck.insert(a + 1, $deck.delete_at(a))
end

# Step 3
b = $deck.index("B")
if b == 53
$deck.insert(2, $deck.pop)
elsif b == 52
$deck.insert(1, $deck.delete_at(b))
else
$deck.insert(b + 2, $deck.delete_at(b))
end

# Step 4
a = $deck.index("A")
b = $deck.index("B")
top = [a, b].min
bottom = [a, b].max
$deck = $deck.values_at((bottom + 1)..53, top..bottom, 0...top)

# Step 5
if $deck[53].kind_of? Integer
$deck = $deck.values_at($deck[53]..52, 0...$deck[53], 53)
end

# Step 5
if $deck[0].kind_of? Integer
if $deck[$deck[0]].kind_of? Integer
key_stream.push($deck[$deck[0]])
end
else
if $deck[53].kind_of? Integer
key_stream.push($deck[53])
end
end
end # Step 7

key_stream.map! do |number|
if number > 26
(number - 26 - 1 + ?A).chr
else
(number - 1 + ?A).chr
end
end
key_stream = key_stream.join("")
i = 5
while i < key_stream.size
key_stream[i, 0] = " "
i += 6
end
return key_stream
end

# Mind reading interface
if ARGV.size == 1 and ARGV[0] =~ /^(?:[A-Z]{5} )*[A-Z]{5}$/
puts decrypt(ARGV[0])
elsif ARGV.size == 1
puts encrypt(ARGV[0])
else
puts "Usage: solitaire.rb MESSAGE"
end

__END__
 
F

Florian Gross

James said:
$deck = $deck.values_at((bottom + 1)..53, top..bottom, 0...top)

Nice. Way better than doing multiple fetches and adding them together.

Regards,
Florian Gross
 
T

Thomas Leitner

Hello!

Very nice quiz!!! :)

Below is my solution.

Greetings,
Thomas


#!/usr/bin/env ruby

require 'optparse'
require 'ostruct'

# Handles the deck
class Deck

# Initializes the deck with the default values
def initialize
@deck = (1..52).to_a << 'A' << 'B'
end

# Operation "move a" (step 2)
def move_A
move_down( @deck.index( 'A' ) )
end

# Operation "move b" (step 3)
def move_B
2.times { move_down( @deck.index( 'B' ) ) }
end

# Operation "triple cut" (step 4)
def triple_cut
a = @deck.index( 'A' )
b = @deck.index( 'B' )
a, b = b, a if a > b
@deck.replace( [@deck[(b + 1)..-1], @deck[a..b], @deck[0...a]].flatten )
end

# Operation "count cut" (step 5)
def count_cut
temp = @deck[0..(@deck[-1] - 1)]
@deck[0..(@deck[-1] - 1)] = []
@deck[-1..-1] = [temp, @deck[-1]].flatten
end

# Operation "output the found letter" (step 6)
def output_letter
a = @deck.first
a = 53 if a.instance_of? String
output = @deck[a]
if output.instance_of? String
nil
else
output -= 26 if output > 26
(output + 64).chr
end
end

# Shuffle the deck using the initialization number +init+ and the method +method+.
# Currently there are only two methods: <tt>:fisher_yates</tt> and <tt>naive</tt>.
def shuffle( init, method = :fisher_yates )
srand( init )
self.send( method.id2name + "_shuffle", @deck )
end

private

# From pleac.sf.net
def fisher_yates_shuffle( a )
(a.size-1).downto(0) { |i|
j = rand(i+1)
a, a[j] = a[j], a if i != j
}
end

# From pleac.sf.net
def naive_shuffle( a )
for i in 0...a.size
j = rand(a.size)
a, a[j] = a[j], a
end
end

# Moves the index one place down while treating the used array as circular list.
def move_down( index )
if index == @deck.length - 1
@deck[1..1] = @deck[index], @deck[1]
@deck.pop
else
@deck[index], @deck[index + 1] = @deck[index + 1], @deck[index]
end
end

end


# Implements the Solitaire Cipher algorithm
class SolitaireCipher

# Initialize the deck
def initialize( init = -1, method = :fisher_yates )
@deck = Deck.new
@deck.shuffle( init, method ) unless init == -1
end

# Decodes the given +msg+ using the internal deck
def decode( msg )
msgNumbers = to_numbers( msg )
cipherNumbers = to_numbers( generate_keystream( msg.length ) )

resultNumbers = []
msgNumbers.each_with_index do |item, index|
item += 26 if item <= cipherNumbers[index]
temp = item - cipherNumbers[index]
resultNumbers << temp
end

return to_chars( resultNumbers )
end

# Encodes the given +msg+ using the internal deck
def encode( msg )
msg = msg.gsub(/[^A-Za-z]/, '').upcase
msg += "X"*(5 - (msg.length % 5)) unless msg.length % 5 == 0

msgNumbers = to_numbers( msg )
cipherNumbers = to_numbers( generate_keystream( msg.length ) )

resultNumbers = []
msgNumbers.each_with_index do |item, index|
temp = item + cipherNumbers[index]
temp = temp - 26 if temp > 26
resultNumbers << temp
end

return to_chars( resultNumbers )
end

private

# Converts the string of uppercase letters into numbers (A=1, B=2, ...)
def to_numbers( chars )
chars.unpack("C*").collect {|x| x - 64}
end

# Converts the array of numbers to a string (1=A, 2=B, ...)
def to_chars( numbers )
numbers.collect {|x| x + 64}.pack("C*")
end

# Generates a keystream for the given +length+.
def generate_keystream( length )
deck = @deck.dup
result = []
while result.length != length
deck.move_A
deck.move_B
deck.triple_cut
deck.count_cut
letter = deck.output_letter
result << letter unless letter.nil?
end
result.join
end

end


options = OpenStruct.new
options.key = -1
options.shuffle = :fisher_yates
options.call_method = :decode

opts = OptionParser.new do |opts|
opts.banner = "Usage: #{File.basename($0, '.*')} [options] message"
opts.separator ""
opts.separator "Options:"
opts.on( "-d", "--decode", "Decode the message" ) do
options.call_method = :decode
end
opts.on( "-e", "--encode", "Encode the message" ) do
options.call_method = :encode
end
opts.on( "-k", "--key KEY", Integer, "Key for shuffling the deck" ) do |key|
options.key = key
end
opts.on( "-m", "--method METHOD", [:fisher_yates, :naive],
"Select the shuffling method (fisher_yates, naive" ) do |method|
options.shuffle = method
end
opts.on( "-h", "--help", "Show help" ) do
puts opts
exit
end
end


if ARGV.length == 0
puts opts
exit
end

message = opts.permute!( ARGV )[0]
cipher = SolitaireCipher.new( options.key, options.shuffle )
puts cipher.send( options.call_method, message )
 
N

Niklas Frykholm

My solution.

----------------------------------------------------------------------

def succ?(*a)
begin
(0..(a.size-2)).each {|i| return false if a.succ != a[i+1]}
return true
rescue
return false
end
end

class Deck
def initialize
@deck = (1..52).to_a + [:A, :B]
end

def move_a
move_down(@deck.index:)A))
end

def move_b
move_down(move_down(@deck.index:)B)))
end

def move_down(index)
if index < 53
new = index + 1
@deck[new], @deck[index] = @deck[index], @deck[new]
else
@deck = @deck[0,1] + @deck[-1,1] + @deck[1..-2]
new = 1
end
return new
end

def triple_cut
jokers = [@deck.index:)A), @deck.index:)B)]
top, bottom = jokers.min, jokers.max
@deck = @deck[(bottom+1)..-1] + @deck[top..bottom] + @deck[0...top]
end

def number_value(x)
return 53 if x == :A or x == :B
return x
end

def count_cut
count = number_value( @deck[-1] )
@deck = @deck[count..-2] + @deck[0,count] + @deck[-1,1]
end

def output
return @deck[ number_value(@deck[0]) ]
end

def update
move_a
move_b
triple_cut
count_cut
end

def get
while true
update
c = output
if c != :A and c != :B
letter = ( ((c-1) % 26) + 65 ).chr
return letter
end
end
end

def to_s
a = []
@deck.each_index {|i|
if succ?(a[-1], @deck, @deck[i+1])
a << "..."
elsif a[-1] == "..." and succ?(@deck[i-1], @deck, @deck[i+1])
# nop
else
a << @deck
end
}
return a.join(" ")
end
end

class Encrypter
def initialize(keystream)
@keystream = keystream
end

def sanitize(s)
s = s.upcase
s = s.gsub(/[^A-Z]/, "")
s = s + "X" * ((5 - s.size % 5) % 5)
out = ""
(s.size / 5).times {|i| out << s[i*5,5] << " "}
return out
end

def mod(c)
return c - 26 if c > 26
return c + 26 if c < 1
return c
end

def process(s, &combiner)
s = sanitize(s)
out = ""
s.each_byte { |c|
if c >= ?A and c <= ?Z
key = @keystream.get
res = combiner.call(c, key[0])
out << res.chr
else
out << c.chr
end
}
return out
end

def encrypt(s)
return process(s) {|c, key| 64 + mod(c + key - 128)}
end

def decrypt(s)
return process(s) {|c, key| 64 + mod(c -key)}
end
end


def test
d = Deck.new()
d.update
puts d

e = Encrypter.new( Deck.new() )
cipher = e.encrypt('Code in Ruby, live longer!')
puts cipher

e = Encrypter.new( Deck.new() )
puts e.decrypt(cipher)

e = Encrypter.new( Deck.new() )
puts e.decrypt("CLEPK HHNIY CFPWH FDFEH")

e = Encrypter.new( Deck.new() )
puts e.decrypt("ABVAW LWZSY OORYK DUPVH")
end

test()
 
G

Glenn M. Lewis

# Cool fun! I found that the majority of my problems came from
# misinterpreting the directions!!! That "vice-versa" thing really
# threw me for a loop for a while (or was that a while-loop?). :)
# Anyway, here is my solution...

#!/usr/bin/env ruby
# Solitaire Cipher
# Ruby-lang quiz #1
# Solution by Glenn M. Lewis - 9/27/04

$debug = nil

class Card
attr_reader :value, :face_value, :suit

def initialize(face_value, suit)
@face_value = face_value
@suit = suit
if suit then
@value = calc_value(face_value, suit)
return
end
case face_value
when "AJoker"
@value = 53
when "BJoker"
@value = 53
else
puts "ERROR: Unknown joker: #{joker}, should be 'AJoker' or 'BJoker'"
exit
end
end

def calc_value(face_value, suit)
val = 0
case suit
when "S" then val += 39
when "H" then val += 26
when "D" then val += 13
when "C"
else
puts "ERROR: Unknown suit: #{suit}, should be C,D,H,S"
end
case face_value
when 2..10 then val += face_value
when "A" then val += 1
when "J" then val += 11
when "Q" then val += 12
when "K" then val += 13
else
puts "ERROR: Unknown card face value: #{face_value}, should be A,2-10,J,Q,K"
end
return val
end
end

class Deck < Array
def initialize
["C", "D", "H", "S"].each do |suit|
["A", 2, 3, 4, 5, 6, 7, 8, 9, 10, "J", "Q", "K"].each do |face_value|
self.push(Card.new(face_value, suit))
end
end
self.push(Card.new("AJoker", nil))
self.push(Card.new("BJoker", nil))
@deck_size = self.size
end

def dump
self.each do |c|
if (c.value == 53)
print c.face_value
else
print c.value
end
print " "
end
print "\n\n"
if (@deck_size != self.size) then
puts "ERROR! Deck size changed to #{self.size}"
exit
end
end

def find_joker(j)
self.each_index do |i|
if (self.face_value == j)
return i
end
end
puts "ERROR: Could not find joker '#{j}' in deck."
end

def move_card_down(pos, num)
print "before move_card_down(#{pos}, #{num}): " if $debug
self.dump if $debug
dest = pos + num
dest -= (self.size-1) if (dest >= self.size)
card = self.delete_at(pos)
temp = self.dup
self.clear
temp.slice(0, dest).each {|x| self.push(x) }
self << card
temp.slice(dest..(-1)).each {|x| self.push(x) }
print "after move_card_down(#{pos}, #{num}): " if $debug
self.dump if $debug
end

def triple_cut_split(a, b)
a,b=b,a if (a > b)
print "before triple_cut_split(#{a}, #{b}): " if $debug
self.dump if $debug
temp = self.dup
self.clear
temp.slice((b+1)..-1).each {|x| self.push(x) }
temp.slice(a..b).each {|x| self.push(x) }
temp.slice(0..(a-1)).each {|x| self.push(x) }
print "after triple_cut_split(#{a}, #{b}): " if $debug
self.dump if $debug
end

def count_cut
print "before count_cut: " if $debug
self.dump if $debug
temp = self.dup
self.clear
num = temp[-1].value
temp.slice(num..-2).each {|x| self.push(x) }
temp.slice(0..(num-1)).each {|x| self.push(x) }
self.push(temp[-1])
print "after count_cut: " if $debug
self.dump if $debug
end

def output_letter
num = self[0].value
card = self[num]
return nil if (card.value == 53)
num = (card.value > 26 ? card.value-26 : card.value)
char = (num-1 + "A"[0]).chr
puts "card.value=#{card.value}, char=#{char}" if $debug
return char
end

def keystream_message(msg)
# result = "DWJXHYRFDGTMSHPUURXJ"
result = ""
while (result.length < msg.length) do
# Step 2 - Move the A Joker down one card
pos = find_joker("AJoker")
move_card_down(pos, 1)
# Step 3 - Move the B Joker down two cards
pos = find_joker("BJoker")
move_card_down(pos, 2)
# Step 4 - Triple cut split around two jokers
apos = find_joker("AJoker")
bpos = find_joker("BJoker")
triple_cut_split(apos, bpos)
# Step 5 - Count cut
count_cut
# Step 6 - Output letter - might be nil
letter = output_letter
result << letter if letter
end
return result
end
end

message = ARGV[0].dup

encrypted = true
encrypted = false if (message =~ /[a-z]/)
words = message.split(/\s+/)
words.each do |word|
encrypted = false if (word.length != 5)
encrypted = false if (word =~ /[^A-Z]/)
end

def message2nums(msg)
result = []
msg.each_byte do |c|
result.push(c+1-"A"[0])
end
return result
end

def nums2message(nums)
result = ""
nums.each do |val|
result << (val-1+"A"[0]).chr
end
return result
end

deck = Deck.new

if encrypted then
puts "Encrypted message: '#{message}'"
message.gsub!(/[^A-Z]/, '')

# Step 1
keystream_message = deck.keystream_message(message)
# puts "keystream_message = #{keystream_message}"

# Step 2
num_message = message2nums(message)
# puts "num_message = "
# p num_message

# Step 3
num_keystream = message2nums(keystream_message)
# puts "num_keystream = "
# p num_keystream

# Step 4
num_result = []
num_message.each_index do |index|
num_result[index] = num_message[index] - num_keystream[index]
num_result[index] += 26 if (num_result[index] < 1)
end

# Step 6
result = nums2message(num_result)
print "Unencrypted message: "
count = 0
result.each_byte do |c|
print c.chr
count += 1
print " " if ((count % 5) == 0)
end
print "\n"

else
puts "Unencrypted message: '#{message}'"

# Step 1
message.upcase!
message.gsub!(/[^A-Z]/, '')
message << "X" * ((message.length % 5)==0 ? 0 : (5-(message.length % 5)))
# puts "message: #{message}"

# Step 2
keystream_message = deck.keystream_message(message)
# puts "keystream_message = #{keystream_message}"

# Step 3
num_message = message2nums(message)
# puts "num_message = "
# p num_message

# Step 4
num_keystream = message2nums(keystream_message)
# puts "num_keystream = "
# p num_keystream

# Step 5
num_result = []
num_message.each_index do |index|
num_result[index] = num_message[index] + num_keystream[index]
num_result[index] -= 26 if (num_result[index] > 26)
end

# Step 6
result = nums2message(num_result)
print "Encrypted message: "
count = 0
result.each_byte do |c|
print c.chr
count += 1
print " " if ((count % 5) == 0)
end
print "\n"
end
 
J

James Edward Gray II

# Cool fun! I found that the majority of my problems came from
# misinterpreting the directions!!! That "vice-versa" thing really
# threw me for a loop for a while (or was that a while-loop?). :)

That's probably largely my fault and I apologize. That would be the
first quiz I ever wrote, so I'm sure I mangled it in places. I'll see
if I can't improve with practice. ;) (Note to self: Don't use
"vice-versa".)
# Anyway, here is my solution...

Thanks for hanging in there and getting a solution together.

James Edward Gray II
 
J

James Edward Gray II

Moin!

Here's my solution for the Solitaire Cipher quiz. It's fairly
class-oriented and longish.

Help. said:
module Solitaire
extend self

# snip module definition...

I'm trying to understand the above trick:

extends self

would be the same as:

self.extends self

Which just leaves the question of what is self? Obviously, it has to
be the module itself. That means we added the methods to the module
itself?

That doesn't make sense alone; however, I know extending a module in
in a class definition adds the module methods as class methods. If I
assume the same is happening here...

My guess: You duplicated all of Solitaires' methods as class methods?

If I'm right, you could have done the same by declaring all those
methods as:

def Solitaire.<method_name>...

Right?

Hmm, wait. Not quite. Then you would be missing the instance methods.

So why did you write it this way?

Big Guess (really reaching now): To provide a convenient interface to
use the module stand-alone while also allowing it to be mixed into
future creations?

Thanks for the lesson.

James Edward Gray II
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,764
Messages
2,569,564
Members
45,039
Latest member
CasimiraVa

Latest Threads

Top