On Sep 26, 2004, at 9:39 AM, Jamis Buck wrote:

> (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__