--------------070103060009070400020400
Content-Type: text/plain; charset=us-ascii; format=flowed
Content-Transfer-Encoding: 7bit
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
--------------070103060009070400020400
Content-Type: text/plain;
name olitaire.rb"
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
filename olitaire.rb"
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 + elf.size if from_index < 0
to_index + elf.size if to_index < 0
item elf.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 ace.to_s.capitalize.gsub(/_(\w)/) { $1.upcase }
type_str ype.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 il)
case key_method
when true then
@deck ard.deck.sort_by { rand }
when String then
@deck ard.deck
generate_letter(key_method)
else
@deck ard.deck
end
end
def generate_letter(seed_phrase il)
if seed_phrase
seed_phrase olitaire.clean(seed_phrase)
seed_phrase il if seed_phrase.empty?
end
result il
until result
deck_size deck.size
# Move JokerA down one card
old_a_pos deck.index(Card::JokerA)
new_a_pos ase 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 ase 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 eed_phrase.slice!(0, 1)
top deck.slice!(0 ... Solitaire.letter_to_number(key))
@deck.insert(-2, *top)
result rue if seed_phrase.empty?
else
# Fetch result
card deck[@deck.first.value]
result ard.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 il, pretty rue)
keystream || eyStream.new
text lean(raw_text)
text + X" * ((text.size / 5.0).ceil * 5 - text.size)
result "
0.upto(text.size - 1) do |index|
source_num etter_to_number(text[index, 1])
key_num etter_to_number(keystream.shift)
result << number_to_letter((source_num + key_num) % 26)
end
result retty(result) if pretty
return result
end
def decrypt(raw_text, keystream il, pretty rue)
keystream || eyStream.new
text lean(raw_text)
result "
0.upto(text.size - 1) do |index|
source_num etter_to_number(text[index, 1])
key_num etter_to_number(keystream.shift)
result << number_to_letter((source_num - key_num) % 26)
end
result retty(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 ile.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] ext
end
opts.on("-k", "--key「ャ
「モ ョ「ャ
「トコ ユ ョ「ゥ
ロコン ココヒモョィゥ
ョィ「ュメ「ャ 「ュュュ 「ャ ノャ
「ユ ョ「ャ
「ヤ ョ ノ クーョ「ャ
「ヤ モヤトマユヤョ「ゥ
ロコン
ロコン
ョィ「ュラ「ャ 「ュュュ 「ャ
「ユ ョ「ャ
「ノ ョ「ャ
「ヤ ュメ ョ「ャ
「ヤ モヤトマユヤョ「ゥ ゜
ロコン ゜
ロコ゜ン ゜
ョ 「「
ョィ「ュ「ャ 「ュュ「ャ
「モ ョ「ゥ
サ
ョ。
ロコン モヤトノホョ
ロコン ッワチィソコロチュレンオワェゥォワレッョィゥ
ロコン
コ
ョィロコンゥ モココフロィイカゥン ョ
「ヒコ 「 ォ モョィゥ
ロコン ココヒモョィゥ
コ゜
ョィロコ゜ンゥョィッワォッゥ
モヤトナメメョ 「ラ ァ ァ ョ「
ュア
゜ ョ
゜ ロコン ッ カ
゜ シ ゜
モヤトナメメョ 「ラ 」゜ ャ「 ォ
「 」゜ 。「
ュイ
ン
ョィ「「ゥョ セ ロコン
シシ ロィ゜ゥン
ョィ「 「ゥ
「ヒコ 「 ォ
「ヒコ 「 ォ モョィゥ
ロコン ココヒモョィゥ
ロコン コ
モョィャ ロコンゥ
ロコン
モヤトナメメョ 「ラチメホノホヌコ ユ 。「
モョィャ ロコンゥ
ュュュュュュュュュュュュュューキーアーウーカーーーケーキーエーーーイーエーーュュ