Advertisement
Guest User

Untitled

a guest
May 29th, 2015
236
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 15.39 KB | None | 0 0
  1.  
  2. /**
  3. * Jesse Weisbeck's Crossword Puzzle (for all 3 people left who want to play them)
  4. *
  5. */
  6. (function($){
  7. $.fn.crossword = function(entryData) {
  8. /*
  9. Qurossword Puzzle: a javascript + jQuery crossword puzzle
  10. "light" refers to a white box - or an input
  11.  
  12. DEV NOTES:
  13. - activePosition and activeClueIndex are the primary vars that set the ui whenever there's an interaction
  14. - 'Entry' is a puzzler term used to describe the group of letter inputs representing a word solution
  15. - This puzzle isn't designed to securely hide answerers. A user can see answerers in the js source
  16. - An xhr provision can be added later to hit an endpoint on keyup to check the answerer
  17. - The ordering of the array of problems doesn't matter. The position & orientation properties is enough information
  18. - Puzzle authors must provide a starting x,y coordinates for each entry
  19. - Entry orientation must be provided in lieu of provided ending x,y coordinates (script could be adjust to use ending x,y coords)
  20. - Answers are best provided in lower-case, and can NOT have spaces - will add support for that later
  21. */
  22.  
  23. var puzz = {}; // put data array in object literal to namespace it into safety
  24. puzz.data = entryData;
  25.  
  26. // append clues markup after puzzle wrapper div
  27. // This should be moved into a configuration object
  28. this.after('<div id="puzzle-clues"><h2>Across</h2><ol id="across"></ol><h2>Down</h2><ol id="down"></ol></div>');
  29.  
  30. // initialize some variables
  31. var tbl = ['<table id="puzzle">'],
  32. puzzEl = this,
  33. clues = $('#puzzle-clues'),
  34. clueLiEls,
  35. coords,
  36. entryCount = puzz.data.length,
  37. entries = [],
  38. rows = [],
  39. cols = [],
  40. solved = [],
  41. tabindex,
  42. $actives,
  43. activePosition = 0,
  44. activeClueIndex = 0,
  45. currOri,
  46. targetInput,
  47. mode = 'interacting',
  48. solvedToggle = false,
  49. z = 0;
  50.  
  51. var puzInit = {
  52.  
  53. init: function() {
  54. currOri = 'across'; // app's init orientation could move to config object
  55.  
  56. // Reorder the problems array ascending by POSITION
  57. puzz.data.sort(function(a,b) {
  58. return a.position - b.position;
  59. });
  60.  
  61. // Set keyup handlers for the 'entry' inputs that will be added presently
  62. puzzEl.delegate('input', 'keyup', function(e){
  63. mode = 'interacting';
  64.  
  65.  
  66. // need to figure out orientation up front, before we attempt to highlight an entry
  67. switch(e.which) {
  68. case 39:
  69. case 37:
  70. currOri = 'across';
  71. break;
  72. case 38:
  73. case 40:
  74. currOri = 'down';
  75. break;
  76. default:
  77. break;
  78. }
  79.  
  80. if ( e.keyCode === 9) {
  81. return false;
  82. } else if (
  83. e.keyCode === 37 ||
  84. e.keyCode === 38 ||
  85. e.keyCode === 39 ||
  86. e.keyCode === 40 ||
  87. e.keyCode === 8 ||
  88. e.keyCode === 46 ) {
  89.  
  90.  
  91.  
  92. if (e.keyCode === 8 || e.keyCode === 46) {
  93. currOri === 'across' ? nav.nextPrevNav(e, 37) : nav.nextPrevNav(e, 38);
  94. } else {
  95. nav.nextPrevNav(e);
  96. }
  97.  
  98. e.preventDefault();
  99. return false;
  100. } else {
  101.  
  102. console.log('input keyup: '+solvedToggle);
  103.  
  104. puzInit.checkAnswer(e);
  105.  
  106. }
  107.  
  108. e.preventDefault();
  109. return false;
  110. });
  111.  
  112. // tab navigation handler setup
  113. puzzEl.delegate('input', 'keydown', function(e) {
  114.  
  115. if ( e.keyCode === 9) {
  116.  
  117. mode = "setting ui";
  118. if (solvedToggle) solvedToggle = false;
  119.  
  120. //puzInit.checkAnswer(e)
  121. nav.updateByEntry(e);
  122.  
  123. } else {
  124. return true;
  125. }
  126.  
  127. e.preventDefault();
  128.  
  129. });
  130.  
  131. // tab navigation handler setup
  132. puzzEl.delegate('input', 'click', function(e) {
  133. mode = "setting ui";
  134. if (solvedToggle) solvedToggle = false;
  135.  
  136. console.log('input click: '+solvedToggle);
  137.  
  138. nav.updateByEntry(e);
  139. e.preventDefault();
  140.  
  141. });
  142.  
  143.  
  144. // click/tab clues 'navigation' handler setup
  145. clues.delegate('li', 'click', function(e) {
  146. mode = 'setting ui';
  147.  
  148. if (!e.keyCode) {
  149. nav.updateByNav(e);
  150. }
  151. e.preventDefault();
  152. });
  153.  
  154.  
  155. // highlight the letter in selected 'light' - better ux than making user highlight letter with second action
  156. puzzEl.delegate('#puzzle', 'click', function(e) {
  157. $(e.target).focus();
  158. $(e.target).select();
  159. });
  160.  
  161. // DELETE FOR BG
  162. puzInit.calcCoords();
  163.  
  164. // Puzzle clues added to DOM in calcCoords(), so now immediately put mouse focus on first clue
  165. clueLiEls = $('#puzzle-clues li');
  166. $('#' + currOri + ' li' ).eq(0).addClass('clues-active').focus();
  167.  
  168. // DELETE FOR BG
  169. puzInit.buildTable();
  170. puzInit.buildEntries();
  171.  
  172. },
  173.  
  174. /*
  175. - Given beginning coordinates, calculate all coordinates for entries, puts them into entries array
  176. - Builds clue markup and puts screen focus on the first one
  177. */
  178. calcCoords: function() {
  179. /*
  180. Calculate all puzzle entry coordinates, put into entries array
  181. */
  182. for (var i = 0, p = entryCount; i < p; ++i) {
  183. // set up array of coordinates for each problem
  184. entries.push(i);
  185. entries[i] = [];
  186.  
  187. for (var x=0, j = puzz.data[i].answer.length; x < j; ++x) {
  188. entries[i].push(x);
  189. coords = puzz.data[i].orientation === 'across' ? "" + puzz.data[i].startx++ + "," + puzz.data[i].starty + "" : "" + puzz.data[i].startx + "," + puzz.data[i].starty++ + "" ;
  190. entries[i][x] = coords;
  191. }
  192.  
  193. // while we're in here, add clues to DOM!
  194. $('#' + puzz.data[i].orientation).append('<li tabindex="1" data-position="' + i + '">' + puzz.data[i].clue + '</li>');
  195. }
  196.  
  197. // Calculate rows/cols by finding max coords of each entry, then picking the highest
  198. for (var i = 0, p = entryCount; i < p; ++i) {
  199. for (var x=0; x < entries[i].length; x++) {
  200. cols.push(entries[i][x].split(',')[0]);
  201. rows.push(entries[i][x].split(',')[1]);
  202. };
  203. }
  204.  
  205. rows = Math.max.apply(Math, rows) + "";
  206. cols = Math.max.apply(Math, cols) + "";
  207.  
  208. },
  209.  
  210. /*
  211. Build the table markup
  212. - adds [data-coords] to each <td> cell
  213. */
  214. buildTable: function() {
  215. for (var i=1; i <= rows; ++i) {
  216. tbl.push("<tr>");
  217. for (var x=1; x <= cols; ++x) {
  218. tbl.push('<td data-coords="' + x + ',' + i + '"></td>');
  219. };
  220. tbl.push("</tr>");
  221. };
  222.  
  223. tbl.push("</table>");
  224. puzzEl.append(tbl.join(''));
  225. },
  226.  
  227. /*
  228. Builds entries into table
  229. - Adds entry class(es) to <td> cells
  230. - Adds tabindexes to <inputs>
  231. */
  232. buildEntries: function() {
  233. var puzzCells = $('#puzzle td'),
  234. light,
  235. $groupedLights,
  236. hasOffset = false,
  237. positionOffset = entryCount - puzz.data[puzz.data.length-1].position; // diff. between total ENTRIES and highest POSITIONS
  238.  
  239. for (var x=1, p = entryCount; x <= p; ++x) {
  240. var letters = puzz.data[x-1].answer.split('');
  241.  
  242. for (var i=0; i < entries[x-1].length; ++i) {
  243. light = $(puzzCells +'[data-coords="' + entries[x-1][i] + '"]');
  244.  
  245. // check if POSITION property of the entry on current go-round is same as previous.
  246. // If so, it means there's an across & down entry for the position.
  247. // Therefore you need to subtract the offset when applying the entry class.
  248. if(x > 1 ){
  249. if (puzz.data[x-1].position === puzz.data[x-2].position) {
  250. hasOffset = true;
  251. };
  252. }
  253.  
  254. if($(light).empty()){
  255. $(light)
  256. .addClass('entry-' + (hasOffset ? x - positionOffset : x) + ' position-' + (x-1) )
  257. .append('<input maxlength="1" val="" type="text" tabindex="-1" />');
  258. }
  259. };
  260.  
  261. };
  262.  
  263. // Put entry number in first 'light' of each entry, skipping it if already present
  264. for (var i=1, p = entryCount; i < p; ++i) {
  265. $groupedLights = $('.entry-' + i);
  266. if(!$('.entry-' + i +':eq(0) span').length){
  267. $groupedLights.eq(0)
  268. .append('<span>' + puzz.data[i].position + '</span>');
  269. }
  270. }
  271.  
  272. util.highlightEntry();
  273. util.highlightClue();
  274. $('.active').eq(0).focus();
  275. $('.active').eq(0).select();
  276.  
  277. },
  278.  
  279.  
  280. /*
  281. - Checks current entry input group value against answer
  282. - If not complete, auto-selects next input for user
  283. */
  284. checkAnswer: function(e) {
  285.  
  286. var valToCheck, currVal;
  287.  
  288. util.getActivePositionFromClassGroup($(e.target));
  289.  
  290. valToCheck = puzz.data[activePosition].answer.toLowerCase();
  291.  
  292. currVal = $('.position-' + activePosition + ' input')
  293. .map(function() {
  294. return $(this)
  295. .val()
  296. .toLowerCase();
  297. })
  298. .get()
  299. .join('');
  300.  
  301. //console.log(currVal + " " + valToCheck);
  302. if(valToCheck === currVal){
  303. $('.active')
  304. .addClass('done')
  305. .removeClass('active');
  306.  
  307. $('.clues-active').addClass('clue-done');
  308.  
  309. solved.push(valToCheck);
  310. solvedToggle = true;
  311. return;
  312. }
  313.  
  314. currOri === 'across' ? nav.nextPrevNav(e, 39) : nav.nextPrevNav(e, 40);
  315.  
  316. //z++;
  317. //console.log(z);
  318. //console.log('checkAnswer() solvedToggle: '+solvedToggle);
  319.  
  320. }
  321.  
  322.  
  323. }; // end puzInit object
  324.  
  325.  
  326. var nav = {
  327.  
  328. nextPrevNav: function(e, override) {
  329.  
  330. var len = $actives.length,
  331. struck = override ? override : e.which,
  332. el = $(e.target),
  333. p = el.parent(),
  334. ps = el.parents(),
  335. selector;
  336.  
  337. util.getActivePositionFromClassGroup(el);
  338. util.highlightEntry();
  339. util.highlightClue();
  340.  
  341. $('.current').removeClass('current');
  342.  
  343. selector = '.position-' + activePosition + ' input';
  344.  
  345. //console.log('nextPrevNav activePosition & struck: '+ activePosition + ' '+struck);
  346.  
  347. // move input focus/select to 'next' input
  348. switch(struck) {
  349. case 39:
  350. p
  351. .next()
  352. .find('input')
  353. .addClass('current')
  354. .select();
  355.  
  356. break;
  357.  
  358. case 37:
  359. p
  360. .prev()
  361. .find('input')
  362. .addClass('current')
  363. .select();
  364.  
  365. break;
  366.  
  367. case 40:
  368. ps
  369. .next('tr')
  370. .find(selector)
  371. .addClass('current')
  372. .select();
  373.  
  374. break;
  375.  
  376. case 38:
  377. ps
  378. .prev('tr')
  379. .find(selector)
  380. .addClass('current')
  381. .select();
  382.  
  383. break;
  384.  
  385. default:
  386. break;
  387. }
  388.  
  389. },
  390.  
  391. updateByNav: function(e) {
  392. var target;
  393.  
  394. $('.clues-active').removeClass('clues-active');
  395. $('.active').removeClass('active');
  396. $('.current').removeClass('current');
  397. currIndex = 0;
  398.  
  399. target = e.target;
  400. activePosition = $(e.target).data('position');
  401.  
  402. util.highlightEntry();
  403. util.highlightClue();
  404.  
  405. $('.active').eq(0).focus();
  406. $('.active').eq(0).select();
  407. $('.active').eq(0).addClass('current');
  408.  
  409. // store orientation for 'smart' auto-selecting next input
  410. currOri = $('.clues-active').parent('ol').prop('id');
  411.  
  412. activeClueIndex = $(clueLiEls).index(e.target);
  413. //console.log('updateByNav() activeClueIndex: '+activeClueIndex);
  414.  
  415. },
  416.  
  417. // Sets activePosition var and adds active class to current entry
  418. updateByEntry: function(e, next) {
  419. var classes, next, clue, e1Ori, e2Ori, e1Cell, e2Cell;
  420.  
  421. if(e.keyCode === 9 || next){
  422. // handle tabbing through problems, which keys off clues and requires different handling
  423. activeClueIndex = activeClueIndex === clueLiEls.length-1 ? 0 : ++activeClueIndex;
  424.  
  425. $('.clues-active').removeClass('.clues-active');
  426.  
  427. next = $(clueLiEls[activeClueIndex]);
  428. currOri = next.parent().prop('id');
  429. activePosition = $(next).data('position');
  430.  
  431. // skips over already-solved problems
  432. util.getSkips(activeClueIndex);
  433. activePosition = $(clueLiEls[activeClueIndex]).data('position');
  434.  
  435.  
  436. } else {
  437. activeClueIndex = activeClueIndex === clueLiEls.length-1 ? 0 : ++activeClueIndex;
  438.  
  439. util.getActivePositionFromClassGroup(e.target);
  440.  
  441. clue = $(clueLiEls + '[data-position=' + activePosition + ']');
  442. activeClueIndex = $(clueLiEls).index(clue);
  443.  
  444. currOri = clue.parent().prop('id');
  445.  
  446. }
  447.  
  448. util.highlightEntry();
  449. util.highlightClue();
  450.  
  451. //$actives.eq(0).addClass('current');
  452. //console.log('nav.updateByEntry() reports activePosition as: '+activePosition);
  453. }
  454.  
  455. }; // end nav object
  456.  
  457.  
  458. var util = {
  459. highlightEntry: function() {
  460. // this routine needs to be smarter because it doesn't need to fire every time, only
  461. // when activePosition changes
  462. $actives = $('.active');
  463. $actives.removeClass('active');
  464. $actives = $('.position-' + activePosition + ' input').addClass('active');
  465. $actives.eq(0).focus();
  466. $actives.eq(0).select();
  467. },
  468.  
  469. highlightClue: function() {
  470. var clue;
  471. $('.clues-active').removeClass('clues-active');
  472. $(clueLiEls + '[data-position=' + activePosition + ']').addClass('clues-active');
  473.  
  474. if (mode === 'interacting') {
  475. clue = $(clueLiEls + '[data-position=' + activePosition + ']');
  476. activeClueIndex = $(clueLiEls).index(clue);
  477. };
  478. },
  479.  
  480. getClasses: function(light, type) {
  481. if (!light.length) return false;
  482.  
  483. var classes = $(light).prop('class').split(' '),
  484. classLen = classes.length,
  485. positions = [];
  486.  
  487. // pluck out just the position classes
  488. for(var i=0; i < classLen; ++i){
  489. if (!classes[i].indexOf(type) ) {
  490. positions.push(classes[i]);
  491. }
  492. }
  493.  
  494. return positions;
  495. },
  496.  
  497. getActivePositionFromClassGroup: function(el){
  498.  
  499. classes = util.getClasses($(el).parent(), 'position');
  500.  
  501. if(classes.length > 1){
  502. // get orientation for each reported position
  503. e1Ori = $(clueLiEls + '[data-position=' + classes[0].split('-')[1] + ']').parent().prop('id');
  504. e2Ori = $(clueLiEls + '[data-position=' + classes[1].split('-')[1] + ']').parent().prop('id');
  505.  
  506. // test if clicked input is first in series. If so, and it intersects with
  507. // entry of opposite orientation, switch to select this one instead
  508. e1Cell = $('.position-' + classes[0].split('-')[1] + ' input').index(el);
  509. e2Cell = $('.position-' + classes[1].split('-')[1] + ' input').index(el);
  510.  
  511. if(mode === "setting ui"){
  512. currOri = e1Cell === 0 ? e1Ori : e2Ori; // change orientation if cell clicked was first in a entry of opposite direction
  513. }
  514.  
  515. if(e1Ori === currOri){
  516. activePosition = classes[0].split('-')[1];
  517. } else if(e2Ori === currOri){
  518. activePosition = classes[1].split('-')[1];
  519. }
  520. } else {
  521. activePosition = classes[0].split('-')[1];
  522. }
  523.  
  524. console.log('getActivePositionFromClassGroup activePosition: '+activePosition);
  525.  
  526. },
  527.  
  528. checkSolved: function(valToCheck) {
  529. for (var i=0, s=solved.length; i < s; i++) {
  530. if(valToCheck === solved[i]){
  531. return true;
  532. }
  533.  
  534. }
  535. },
  536.  
  537. getSkips: function(position) {
  538. if ($(clueLiEls[position]).hasClass('clue-done')){
  539. activeClueIndex = position === clueLiEls.length-1 ? 0 : ++activeClueIndex;
  540. util.getSkips(activeClueIndex);
  541. } else {
  542. return false;
  543. }
  544. }
  545.  
  546. }; // end util object
  547.  
  548.  
  549. puzInit.init();
  550.  
  551.  
  552. }
  553.  
  554. })(jQuery);
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement