# 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[i].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