Skip to content

Commit

Permalink
Quiz 1: Solitaire
Browse files Browse the repository at this point in the history
  • Loading branch information
mattwildig committed Mar 7, 2012
0 parents commit b4580c7
Show file tree
Hide file tree
Showing 3 changed files with 340 additions and 0 deletions.
58 changes: 58 additions & 0 deletions 1_solitaire/sol-test.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
Plaintext: AAAAAAAAAAAAAAA
Key: <null key>
Output: 4 49 10 53 24 8 51 44 6 4 33 20 39 19 34 42
Ciphertext: EXKYI ZSGEH UNTIQ

Plaintext: AAAAAAAAAAAAAAA
Key: 'f'
Output: 49 24 8 46 16 1 12 33 10 10 9 27 4 32 24
Ciphertext: XYIUQ BMHKK JBEGY

Plaintext: AAAAAAAAAAAAAAA
Key: 'fo'
Output: 19 46 9 24 12 1 4 43 11 32 23 39 29 34 22
Ciphertext: TUJYM BERLG XNDIW

Plaintext: AAAAAAAAAAAAAAA
Key: 'foo'
Output: 8 19 7 25 20 53 9 8 22 32 43 5 26 17 53 38 48
Ciphertext: ITHZU JIWGR FARMW

Plaintext: AAAAAAAAAAAAAAA
Key: 'a'
Output: 49 14 3 26 11 32 18 2 46 37 34 42 13 18 28
Ciphertext: XODAL GSCUL IQNSC

Plaintext: AAAAAAAAAAAAAAA
Key: 'aa'
Output: 14 7 32 22 38 23 23 2 26 8 12 2 34 16 15
Ciphertext: OHGWM XXCAI MCIQP

Plaintext: AAAAAAAAAAAAAAA
Key: 'aaa'
Output: 3 28 18 42 24 33 1 16 51 53 39 6 29 43 46 45
Ciphertext: DCSQY HBQZN GDRUT

Plaintext: AAAAAAAAAAAAAAA
Key: 'b'
Output: 49 16 4 30 12 40 8 19 37 25 47 29 18 16 18
Ciphertext: XQEEM OITLZ VDSQS

Plaintext: AAAAAAAAAAAAAAA
Key: 'bc'
Output: 16 13 32 17 10 42 34 7 2 37 6 48 44 28 53 4
Ciphertext: QNGRK QIHCL GWSCE

Plaintext: AAAAAAAAAAAAAAA
Key: 'bcd'
Output: 5 38 20 27 50 1 38 26 49 33 39 42 49 2 35
Ciphertext: FMUBY BMAXH NQXCJ

Plaintext: AAAAAAAAAAAAAAAAAAAAAAAAA
Key: 'cryptonomicon'
Ciphertext: SUGSR SXSWQ RMXOH IPBFP XARYQ

Plaintext: SOLITAIRE
Key: 'cryptonomicon'
Ciphertext: KIRAK SFJAN

151 changes: 151 additions & 0 deletions 1_solitaire/solitaire.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
module Solitaire

UNKEYED_DECK = (1..54).to_a

module Useful
def ascii_to_a1 codepoint
codepoint - ?A.ord + 1
end

def a1_to_ascii a1
a1 -= 26 if a1 > 26
a1 += 26 if a1 < 1
a1 + ?A.ord - 1
end

def group_in_fives text
count = 0
text.each_char.inject('') do |out, char|
out << char
count += 1
(out << ' '; count = 0) if count == 5
out
end.strip
end
end

class KeyStream
include Useful
attr_reader :deck

def initialize(deck)
@deck = deck.dup
end

def next_key
joker_A_down_1
joker_B_down_2
triple_cut
count_cut_bottom
o = output_letter
o == 54 ? 53 : o
end

def next_letter
n = next_key
n = next_key while n == 53
n % 26
end

def joker_A_down_1
move_down 53, 1
end

def joker_B_down_2
move_down 54, 2
end

def move_down(card, dist)
pos = @deck.index(card)
@deck.delete_at(pos)
pos -= @deck.length if (pos + dist) > @deck.length
@deck.insert(pos + dist, card)
end

def triple_cut
a, b = @deck.index(53), @deck.index(54)
a, b = b, a if b < a
@deck = @deck[(b+1)..-1] + @deck[a..b] + @deck[0...a]
end

def count_cut_bottom
count_cut @deck.last
end

def count_cut count
count = 53 if count == 54
@deck = @deck[count..-2] + @deck[0, count] + [@deck.last]
end

def output_letter
val = @deck[0]
val = val == 54 ? 53 : val
@deck[val]
end

def key_deck char
joker_A_down_1
joker_B_down_2
triple_cut
count_cut_bottom
count_cut ascii_to_a1(char)
end
end

class Encrypter
include Useful

def initialize keystream
@keystream = keystream
end

def encrypt plaintext
plaintext.gsub! /[^A-Za-z]/, ''
plaintext.upcase!
plaintext << 'X' * (5 - plaintext.length % 5) if plaintext.length % 5 != 0

ciphertext = plaintext.codepoints.inject('') do |encrypted, char|
char = ascii_to_a1(char)
char += @keystream.next_letter
encrypted << a1_to_ascii(char).chr
end

group_in_fives(ciphertext)
end
end

class Decrypter
include Useful

def initialize keystream
@keystream = keystream
end

def decrypt ciphertext
ciphertext.codepoints.inject('') do |plain, cp|
next(plain << ' ') if cp == ' '.ord
cp = ascii_to_a1(cp)
cp -= @keystream.next_letter
plain << a1_to_ascii(cp)
end
end
end

def self.encrypt message, keystream
Encrypter.new(KeyStream.new(keystream)).encrypt(message)
end

def self.decrypt ciphertext, keystream
Decrypter.new(KeyStream.new(keystream)).decrypt(ciphertext)
end

def self.key_deck passphrase
passphrase.upcase!
k = KeyStream.new UNKEYED_DECK
passphrase.codepoints do |cp|
k.key_deck cp
end
k.deck
end

end
131 changes: 131 additions & 0 deletions 1_solitaire/test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
require 'test/unit'
require './solitaire'

JOKER_A = 53
JOKER_B = 54

UNKEYED_DECK = (1..54).to_a
UNKEYED_RESULTS = [4, 49, 10, JOKER_A, 24, 8, 51, 44, 6, 4, 33]
UNKEYED_LETTERS = [4, 23, 10, 24, 8, 25, 18, 6, 4, 7]

class SolitaireTest < Test::Unit::TestCase

def test_keystream_create
assert Solitaire::KeyStream.new(UNKEYED_DECK).is_a? Solitaire::KeyStream
end

def test_keystream_joker_A_down_1
s = Solitaire::KeyStream.new [1,2,JOKER_A,3,4]
s.joker_A_down_1
assert_equal [1,2,3,JOKER_A,4], s.deck

s = Solitaire::KeyStream.new [1,2,3,4,JOKER_A]
s.joker_A_down_1
assert_equal [1, JOKER_A, 2, 3, 4], s.deck
end

def test_keystream_joker_B_down_2
s = Solitaire::KeyStream.new [1, 2, JOKER_B, 3, 4]
s.joker_B_down_2
assert_equal [1, 2, 3, 4, JOKER_B], s.deck

s = Solitaire::KeyStream.new [1,2,3,JOKER_B,4]
s.joker_B_down_2
assert_equal [1, JOKER_B, 2, 3, 4], s.deck
end

def test_keystream_triple_cut
s = Solitaire::KeyStream.new [1, 2, JOKER_A, 3, 4, JOKER_B, 5, 6]
s.triple_cut
assert_equal [5, 6, JOKER_A, 3, 4, JOKER_B, 1, 2], s.deck

s = Solitaire::KeyStream.new [1, JOKER_B, 2, 3, 4, JOKER_A, 5, 6]
s.triple_cut
assert_equal [5, 6, JOKER_B, 2, 3, 4, JOKER_A, 1], s.deck

s = Solitaire::KeyStream.new [JOKER_B, 1, 2, 3, 4, 5, 6, JOKER_A]
s.triple_cut
assert_equal [JOKER_B, 1, 2, 3, 4, 5, 6, JOKER_A], s.deck
end

def test_keystream_count_cut_bottom
s = Solitaire::KeyStream.new [1, 2, JOKER_A, 3, 4, JOKER_B, 5, 6]
s.count_cut_bottom
assert_equal [5, 1, 2, JOKER_A, 3, 4, JOKER_B, 6], s.deck
end

def test_keystream_output_letter
s = Solitaire::KeyStream.new [3, 1, 2, 4, 5, 6]
assert_equal 4, s.output_letter
end

def test_keystream_next_key
s = Solitaire::KeyStream.new UNKEYED_DECK
n = s.next_key
assert_equal 4, n
end

def test_keystream_unkeyed_sequence
s = Solitaire::KeyStream.new UNKEYED_DECK
UNKEYED_RESULTS.each do |result|
assert_equal result, s.next_key#, s.deck.to_s
end
end

def test_keystream_unkeyed_sequence_letters
s = Solitaire::KeyStream.new UNKEYED_DECK
UNKEYED_LETTERS.each do |result|
assert_equal result, s.next_letter#, s.deck.to_s
end
end

def test_encrypter_create
e = Solitaire::Encrypter.new(Solitaire::KeyStream.new(UNKEYED_DECK))
assert e.is_a? Solitaire::Encrypter
end

def test_encrypter_unkeyed
e = Solitaire::Encrypter.new(Solitaire::KeyStream.new(UNKEYED_DECK))
assert_equal "GLNCQ MJAFF FVOMB JIYCB", e.encrypt("CODEI NRUBY LIVEL ONGER")
end

def test_encrypter_unkeyed
d = Solitaire::Decrypter.new(Solitaire::KeyStream.new(UNKEYED_DECK))
assert_equal "CODEI NRUBY LIVEL ONGER", d.decrypt("GLNCQ MJAFF FVOMB JIYCB")
end

def test_module_encrypt_unkeyed
assert_equal "GLNCQ MJAFF FVOMB JIYCB", Solitaire.encrypt("Code in Ruby, live longer", UNKEYED_DECK)
end

def test_module_decrypt_unkeyed
assert_equal "CODEI NRUBY LIVEL ONGER", Solitaire.decrypt("GLNCQ MJAFF FVOMB JIYCB", UNKEYED_DECK)
end

def test_examples
assert_equal "YOURC IPHER ISWOR KINGX", Solitaire.decrypt("CLEPK HHNIY CFPWH FDFEH", UNKEYED_DECK)
assert_equal "WELCO METOR UBYQU IZXXX", Solitaire.decrypt("ABVAW LWZSY OORYK DUPVH", UNKEYED_DECK)
end

def self.setup_test hash
hash['Key'] = hash['Key'].gsub /'/, ''
deck = hash['Key'] == '<null key>' ? UNKEYED_DECK : Solitaire.key_deck(hash['Key'])

define_method "test_#{hash.object_id}" do
assert_equal hash['Ciphertext'], Solitaire.encrypt(hash['Plaintext'], deck)
end
end

File.foreach('sol-test.txt').inject({}) do |hash, line|
next(hash) if line.strip == ''
k, v = line.split(':').map(&:strip)
hash[k] = v

if k == 'Ciphertext'
setup_test hash
next {}
end
hash
end

end

0 comments on commit b4580c7

Please sign in to comment.