More JavaScript encoding

Well, the next cipher my son is going to study with his math group is the Vigenére cipher, which is like the Caesar cipher, but with a shift that changes with each letter of the plaintext according to a key. Here’s the HTML/JavaScript page I made up to show him how this new procedure can be translated from actions he does by hand to a programming language.

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
   "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
   <title>Vigen&eacute;re Cipher</title>
   <script>

   // This function takes a string (textIn), another sting (key),
   // a boolean (reverse), and another boolean (lettersOnly), and
   // returns an encrypted or decrypted string using the Vigenere cipher. 
   function vigenere(textIn, key, reverse, lettersOnly) {
      var alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
      var textOut = "";
      var n = textIn.length;
      var m = key.length;

      // We only use upper case letters.
      textIn = textIn.toUpperCase();
      key = key.toUpperCase();

      // Go through the input, shifting letters and composing the output.
      // i keeps track of the character position, j keeps track of the
      // letter position. We need to keep track of letter position, because
      // we overlay the key on letters only, not spaces or punctuation.
      for (var i=0, j=0; i<n; i++) {

         // Get the next character and its position in the alphabet.
         // 'indexOf' returns -1 if the character isn't found.
         var c = textIn.charAt(i);
         var position = alphabet.indexOf(c);

         // If it's a letter...
         if (position >= 0) {

            // Calculate the shift from the key. We use j instead of i
            // in the 'charAt' functions because the key is used to shift
            // letters only. The '% m' part starts the key over again when
            // we've reached its end.
            var keyPosition = key.charAt(j % m);
            var charShift = alphabet.indexOf(keyPosition);

            // Account for the direction.
            if (reverse) charShift = 26 - charShift;

            // Calculate the shifted position. The '% 26' part
            // "circles around" to the beginning after Z.
            var newPosition = (position + charShift) % 26;

            // Add the new letter to the output string.
            textOut += alphabet.charAt(newPosition);

            // Increment position for next letter.
            j++;
         }

         // If it's not a letter...
         else {

            // If we should ignore non-letters...
            if (lettersOnly) {
               // Do nothing.
            }
            // If we should include non-letters...
            else {
               // Pass through non-letters without shifting.
               textOut += c;
            }
         }
      }

      // Return the output.
      return textOut;
   }

   // This function takes a string and returns it without non-letters.
   function cleanString(str) {
      var alphabet = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
      var newStr = "";
      var n = str.length;

      // Go through the input string, adding only letters to the output
      // string.
      for (var i=0; i<n; i++) {
         var c = str.charAt(i);
         if (alphabet.indexOf(c) >= 0) newStr += c;
      }

      // Return the stripped string.
      return newStr;
   }

   // This function collects the information from the form elements on
   // the page, calls the encryption/decryption function ('vigenere'), and
   // puts the output into its form element.
   function doVigenere() {
      var textIn = document.getElementById("in").value;
      var outField = document.getElementById("out");
      var keyField = document.getElementById("key");
      var key = keyField.value;
      var lettersOnly = document.getElementById("letters").checked;
      var reverse;

      // Strip the key of non-letters.
      key = cleanString(key);
      keyField.value = key;

      // Figure out the direction of the encryption/decryption.
      if (document.getElementById("direction").value == "reverse")
         reverse = true;
      else
         reverse = false;

      outField.value = vigenere(textIn, key, reverse, lettersOnly);
   }

   </script>
   <style type="text/css">
      textarea, input#key {
         font-family: monospace;
         font-size: medium;
      }
   </style>
</head>
<body>
   <h1>Vigen&eacute;re Cipher</h1>
   <form  onsubmit="return false;">
      <p>Input text:<br>
      <textarea id="in" cols="50" rows="8">With great power comes great responsibility.</textarea></p>
      <p>Key:<br>
         <input type="text" id="key" size="50" value="Spiderman"></p>
      <p>Direction:
         <select id="direction">
            <option selected="selected">forward</option>
            <option>reverse</option>
         </select>
       &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
         <input type="checkbox" id="letters" value="yes">Letters only 
         &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
         <input type="button" value="Encipher" onclick="doVigenere()"></p>
      <p>Output text:<br>
      <textarea id="out" cols="50" rows="8">OXBK KIQAG HDEHV TAMRK VZHEK DEFHDVVMSULVLN.</textarea></p>
   </form>
</body>
</html>

As with my earlier page that did the Caesar cipher, this one includes more comments and has its code broken into more lines than is normal for my programs. And for the same reason: my real audience is a ten-year-old. It does, however, sneak two initializations into the for loop of the vigenere function, which may turn out to be a bit too clever.

Tags: