[Florian Gross <flgr / ccan.de>, 2004-09-26 16.24 CEST]
> 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(" ")