Last active
December 26, 2015 09:19
Cryptogram Interactive Puzzle
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!-- Cryptogram Interactive Puzzle | |
Solve the puzzle by replacing letters to reveal the hidden message. | |
Use the keyboard and mouse to move the cursor. | |
Type a letter to make a guess and advance the cursor. | |
Correct mistakes by typing over them or with backspace and delete. | |
--> | |
<style> | |
.puzzle { font-family: monospace; font-size: 2em } | |
.guess { font-style: normal; font-weight: bold; color: darkslateblue} | |
.solved { color: darkgreen} | |
.error { color: red } | |
.cursor { background-color: lightgray } | |
.curchr { text-decoration: underline } | |
</style> | |
<body> | |
<h1>Cryptogram</h1> | |
<p>Solve the puzzle by replacing letters to reveal the hidden message.</p> | |
<p>Use the keyboard and mouse to move the cursor. | |
Type a letter to enter a guess and advance the cursor. | |
Correct mistakes by typing over them or with backspace and delete.</p> | |
<p id="puzzle"></p> | |
<script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script> | |
<script> | |
var alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; | |
var encodeCypher = {}; | |
var decodeCypher = {}; | |
var textClear = ""; | |
var textAlpha = ""; | |
var textKey = ""; | |
var textHidden = ""; | |
var textSolved = ""; | |
function setPuzzle(text) { | |
textClear = text; | |
textAlpha = makeAlpha(textClear); | |
textKey = makeKey(textAlpha.length); | |
encodeCypher = makeCypher(textAlpha, textKey); | |
decodeCypher = makeCypher(textKey, textKey.toLowerCase()); | |
textHidden = translate(textClear, encodeCypher, true); | |
textSolved = translate(textHidden, decodeCypher, true); | |
} | |
function setGuess(cypherVal, clearVal) { | |
cypherVal = cypherVal.toUpperCase(); | |
clearVal = clearVal.toUpperCase(); | |
if(decodeCypher.hasOwnProperty(cypherVal)) { | |
decodeCypher[cypherVal] = clearVal; | |
textSolved = translate(textHidden, decodeCypher, true); | |
} | |
} | |
function clearGuess(cypherVal) { | |
cypherVal = cypherVal.toUpperCase(); | |
if(decodeCypher.hasOwnProperty(cypherVal)) { | |
decodeCypher[cypherVal] = cypherVal.toLowerCase(); | |
textSolved = translate(textHidden, decodeCypher, true); | |
} | |
} | |
function makeCypher(from, to) { | |
var cypher = {}; | |
for(var i = 0; i < from.length; ++i) { | |
cypher[from.charAt(i)] = to.charAt(i); | |
} | |
return cypher; | |
} | |
function translate(text, cypher, preserve) { | |
var arry = text.split(""); | |
for(var i = 0; i < arry.length; ++i) { | |
var arryval = arry[i].toUpperCase(); | |
if(cypher.hasOwnProperty(arryval)) { | |
var cypherval = cypher[arryval]; | |
if(!preserve) { | |
arry[i] = cypherval; | |
} else { | |
if(arryval != arry[i]) { | |
arry[i] = cypherval.toLowerCase(); | |
} else { | |
arry[i] = cypherval.toUpperCase(); | |
} | |
} | |
} | |
} | |
return arry.join(""); | |
} | |
function makeAlpha(text) { | |
var arry = text.toUpperCase().replace(/[^A-Z]/g,"").split(""); | |
arry.sort(); | |
var i = 0, j = 0; | |
while(j < arry.length) { | |
arry[i] = arry[j]; | |
while(j < arry.length && arry[i] === arry[j]) { | |
++j; | |
} | |
++i; | |
} | |
return arry.slice(0, i).join(""); | |
} | |
function scramble(alpha) { | |
var arry = alpha.split(""); | |
for(var i = arry.length; 0 < i; --i) { | |
var j = Math.floor(i * Math.random()); | |
var tmp = arry[i-1]; | |
arry[i-1] = arry[j]; | |
arry[j] = tmp; | |
} | |
return arry.join(""); | |
} | |
function makeKey(length) { | |
return scramble(alphabet).substr(0, length); | |
} | |
</script> | |
<script> | |
var puzzleOptions = [ | |
"By three methods we may learn wisdom: first, by reflection, which is noblest; second, by imitation, which is easiest; and third, by experience, which is the bitterest. --Confucius", | |
"Computers make it easier to do a lot of things, but most of the things they make it easier to do don't need to be done. --Andy Rooney", | |
"The typewriting machine, when played with expression, is no more annoying than the piano when played by a sister or near relation. --Oscar Wilde.", | |
"The first rule of any technology used in a business is that automation applied to an efficient operation will magnify the efficiency. The second is that automation applied to an inefficient operation will magnify the inefficiency. --Bill Gates", | |
]; | |
setPuzzle(puzzleOptions[Math.floor(Math.random()*puzzleOptions.length)]); | |
var cursorPos = 0; | |
var cursorBlink = true; | |
var cursorTimer = null; | |
var puzzle = d3.selectAll("#puzzle"); | |
puzzle.classed("puzzle", true); | |
var data = puzzle.selectAll("*").data(d3.range(textSolved.length)); | |
data.enter().append("span"); | |
data.exit().remove(); | |
data.order(); | |
function getTextAt(i) { | |
return textSolved.charAt(i); | |
} | |
function getGuessAt(i) { | |
var hiddenVal = textHidden.charAt(i).toUpperCase(); | |
if(!decodeCypher.hasOwnProperty(hiddenVal)) { | |
return false; | |
} else { | |
var decodeVal = decodeCypher[hiddenVal]; | |
return decodeVal === decodeVal.toUpperCase(); | |
} | |
} | |
function update() { | |
var curChr = textHidden.charAt(cursorPos).toUpperCase(); | |
if (curChr === curChr.toLowerCase()) | |
curChr = undefined; | |
var guessCount = {}; | |
for(var k in decodeCypher) { | |
var decodeVal = decodeCypher[k]; | |
if(decodeVal === decodeVal.toUpperCase()) { | |
guessCount[decodeVal] = 1 + (guessCount[decodeVal] || 0); | |
} | |
} | |
function getErrorAt(i) { | |
if(!getGuessAt(i)) { | |
return false; | |
} else { | |
var decodeVal = textSolved.charAt(i).toUpperCase(); | |
return guessCount[decodeVal] > 1; | |
} | |
} | |
var elems = puzzle.selectAll("*"); | |
elems.text(getTextAt); | |
elems.classed("guess", getGuessAt); | |
elems.classed("error", getErrorAt); | |
elems.classed("curchr", function(i) { | |
return curChr === textHidden.charAt(i).toUpperCase(); | |
}); | |
elems.classed("solved", textSolved === textClear); | |
elems.on("click", cursorUpdate); | |
} | |
function cursorCallback() { | |
var data = puzzle.selectAll("*"); | |
data.classed("cursor", function(d) { | |
return d === cursorPos ? cursorBlink : false; }); | |
cursorBlink = !cursorBlink; | |
} | |
function cursorUpdate(pos) { | |
if(cursorTimer != null) { | |
clearInterval(cursorTimer); | |
} | |
while(pos < 0) { | |
pos += textHidden.length; | |
} | |
while(textHidden.length <= pos) { | |
pos -= textHidden.length; | |
} | |
cursorPos = pos; | |
cursorBlink = true; | |
update(); | |
cursorCallback(); | |
cursorTimer = setInterval(cursorCallback, 800); | |
} | |
function setGuessAtCursor(val) { | |
setGuess(textHidden.charAt(cursorPos), val); | |
update(); | |
} | |
function clearGuessAtCursor() { | |
clearGuess(textHidden.charAt(cursorPos)); | |
update(); | |
} | |
function updateKey(key) { | |
switch(key) { | |
case 37: //left arrow | |
cursorUpdate(cursorPos-1); | |
return true; | |
case 32: //space | |
case 39: //right arrow | |
cursorUpdate(cursorPos+1); | |
return true; | |
case 36: //home | |
cursorUpdate(0); | |
return true; | |
case 35: //end | |
cursorUpdate(-1); | |
return true; | |
case 8: //backspace | |
cursorUpdate(cursorPos-1); | |
clearGuessAtCursor(); | |
return true; | |
case 46: //delete | |
clearGuessAtCursor(); | |
return true; | |
} | |
// letter | |
if(65 <= key && key < 91) { | |
var val = String.fromCharCode(key).toUpperCase(); | |
setGuessAtCursor(val.toUpperCase()); | |
cursorUpdate(cursorPos+1); | |
return true; | |
} | |
return false | |
} | |
function keyDownCallback() { | |
if(updateKey(d3.event.keyCode || d3.event.which)) | |
d3.event.preventDefault(); | |
} | |
d3.selectAll("body").on("keydown", keyDownCallback); | |
cursorUpdate(0); | |
</script> | |
</body> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment