Software Secret Weapons™

 
JavaScript Crossword Engine – Source Released LGPL
by Pavel Simakov on 2009-12-29 20:59:43 under AJAX, Text Mining, view comments
Bookmark and Share
 

Giving It Away

Almost three years ago I developed JavaScript Crossword Engine. After hundreds of requests from many of the readers, I am finally releasing all of the source code to the community under LGPL. The source code and a short tutorial can be found in oy-cword-1.0.zip. Enjoy!

Anatomy of JavaScript Crossword

Download and open the ZIP file above. Find the file puzzle-1.html and open it up with the editor. You will now learn how to step-by-step create a puzzle just like this one here:

Give Puzzle HTML Skeleton

First thing you will see is a block of HTML that defines the puzzle structural skeleton. The individual components have unique ids prefixed with "oyg". You can change this HTML to move different components around. Make sure that you keep the element ids as they are, because these ids are used to inject and bind runtime objects of the crossword. The class names are prefixed with "oy". You are free to change the class names as well to match your website look & feel.

	
<!-- 
	here we have oygContext top DOM element to which the play area will be bound;
	the HTML template has more binding sites or various visual parts of the puzzle: 
	oygHeader, oygHeaderMenu, oygState, oygPuzzle, oygPuzzleFooter, oygListH, oygListV, oygFooter;
	all element names for binding are prefixed with "oyg"
-->
<div id="oygContext" align="center" style="width:100%;">
	<table class="oyOuterFrame" border="0" cellpadding="0" cellspacing="0">
		<tr><td align="center">
			<table class="oyFrame" border="0" cellpadding="0" cellspacing="0">
				<tr>
				<td colspan="5">
				<table class="oyFrame" border="0" cellpadding="0" 
					cellspacing="0" width="100%">
					<tr class="oyHeader">
						<td class="oyHeader">
							<div id="oygHeader"></div>
						</td>
						<td align="right">	
							<div id="oygHeaderMenu"></div>
						</td> 
					</tr>					
				</table>
				</td>  
				</tr>
				<tr style="height: 4px;">
					<td colspan="5"></td>
				</tr>
				<tr>  
					<td rowspan="3" class="oyPuzzleCell" align="center" valign="top"> 
						<div id="oygState"></div>
						<div class="oyPuzzle" id="oygPuzzle"></div>
						<div class="oyPuzzleFooter" id="oygPuzzleFooter"></div>			
					</td>  
					<td class="oyListCellDot">.</td>    
					<td class="oyListCell" valign="top" id="oygListH"></td>
				</tr>
				<tr style="height: 4px;">
					<td colspan="4"></td>
				</tr>		
				<tr>  
					<td class="oyListCellDot">.</td>   
					<td class="oyListCell" valign="top" id="oygListV"></td>
				</tr>
				<tr style="height: 4px;">
					<td colspan="5"></td>
				</tr>			
				<tr>
					<td colspan="5" class="oyFooter"> 
						<div id="oygFooter"></div>
					</td>
				</tr>			
			</table>
		</td></tr>
	</table>
	<div id="oygStatic" align="center" style="font-size: 10px; color: #4282B5; font-family: Arial;"></div>
</div>

Include JavaScript & CSS files

Secondly, you need to include appropriate JavaScript and CSS files. You can merge all files into one to reduce loading time. Please make sure that the order in which individual files are merged is the same as shown below. You can also load these files in the HEAD section of HTML document.

	
        <!-- 
		here we include all oy-cword-1.0 CSS files; all our style class name are prefixed with "oy"
	-->
	<link rel="stylesheet" href="./oy-cword-1.0/css/base.css" type="text/css">

	<!-- 
		here we include all oy-cword-1.0 JavaScript files; order is important; 
		all the files can be combined into one file to reduce number of separate requests
	-->
	<script type="text/javascript" src="./oy-cword-1.0/js/oyPrologue.js"></script>	
	<script type="text/javascript" src="./oy-cword-1.0/js/oyJsrAjax.js"></script>	
	<script type="text/javascript" src="./oy-cword-1.0/js/oyClue.js"></script>	
	<script type="text/javascript" src="./oy-cword-1.0/js/oyMenu.js"></script>	
	<script type="text/javascript" src="./oy-cword-1.0/js/oyPuzzle.js"></script>	
	<script type="text/javascript" src="./oy-cword-1.0/js/oyServer.js"></script>	
	<script type="text/javascript" src="./oy-cword-1.0/js/oySign.js"></script>	
	<script type="text/javascript" src="./oy-cword-1.0/js/oyMisc.js"></script>	

Configure the puzzle object

Next step is to configure the puzzle object.

	
	<script type="text/javascript"><!--

		// 
		//	here we include our own puzzle; it has to be fully prepared with 
		//	all words properly arranged on the grid;
		//	currently only one instance of the puzzle can be embedded 
		//	into the page, we may fix this in the future
		// 

		var oygCrosswordPuzzle = new oyCrosswordPuzzle (
		  "5748185539682739085",
		  "./oy-cword-1.0",
		  "/a/a",
		  "Gang Of Four &#x28;GOF&#x29; Software Design Patterns Crossword",
		  "This crossword tests your knowledge of software design patterns.",
		  [
			 new oyCrosswordClue(8, 
				"This factory creates an instance of several families of classes", 
				"Abstract", "26f265b96e01081a5ef26a432eda9cff", 1, 12, 6)
			,new oyCrosswordClue(7, 
				"Separates object construction from its representation", 
				"Builder", "88a259cdfe3cb78a40ec120a63fac540", 0, 12, 7)
			,new oyCrosswordClue(7, 
				"This method creates an instance of several derived classes", 
				"Factory", "c1fa41b9627f8ea09e5a2a6ee22d6bc5", 0, 9, 9)
			,new oyCrosswordClue(9, 
				"A fully initialized instance to be copied or cloned", 
				"Prototype", "394941ddf24e90c70298d4c2750db4d9", 0, 9, 13)
			,new oyCrosswordClue(9, 
				"A class of which only a single instance can exist", 
				"Singleton", "c31a3cbd6db754a0e4ac5fd8e6ec3e54", 1, 17, 2)
			,new oyCrosswordClue(7, 
				"Match interfaces of different classes", 
				"Adapter", "76fb70e9d93dedce00308eeb0c304412", 1, 10, 7)
			,new oyCrosswordClue(6, 
				"Separates an object's interface from its implementation", 
				"Bridge", "3a8203786f83c06011189ab882b1894c", 0, 13, 5)
			,new oyCrosswordClue(9, 
				"A tree structure of simple and composite objects", 
				"Composite", "0de044b990184a9d27dddf0f5e6806af", 0, 11, 3)
			,new oyCrosswordClue(9, 
				"Add responsibilities to objects dynamically", 
				"Decorator", "d51d27de85dae46db969b1012a317f12", 0, 1, 19)
			,new oyCrosswordClue(6, 
				"A single class that represents an entire subsystem", 
				"Facade", "815dbf1b993d1c86356056c4ba416fb2", 1, 3, 0)
			,new oyCrosswordClue(9, 
				"A fine-grained instance used for efficient sharing", 
				"Flyweight", "d87301a9c82ad02e5de19981236773d6", 0, 2, 11)
			,new oyCrosswordClue(5, 
				"An object representing another object", 
				"Proxy", "a8b79bae14cff23069a1793ba55f2966", 1, 4, 7)
			,new oyCrosswordClue(14, 
				"A way of passing a request between a chain of objects - chain of __", 
				"Responsibility", "d29967be8acd1416731808c024de639c", 1, 7, 4)
			,new oyCrosswordClue(7, 
				"Encapsulate a command request as an object", 
				"Command", "f3a3c64bbcdc310786988833c04d1ba4", 1, 1, 0)
			,new oyCrosswordClue(11, 
				"A way to include language elements in a program", 
				"Interpreter", "d078000250c55212b9584cc691119138", 0, 0, 5)
			,new oyCrosswordClue(8, 
				"Sequentially access the elements of a collection", 
				"Iterator", "1ea9447c03c6840641f5abd5f284c3c2", 0, 1, 8)
			,new oyCrosswordClue(8, 
				"Defines simplified communication between classes", 
				"Mediator", "90740e7f8d0d00a7f7e39ee8b40e85c1", 0, 2, 16)
			,new oyCrosswordClue(7, 
				"Capture and restore an object's internal state", 
				"Memento", "97e8a2700b6a48922738058cd57cd0ab", 1, 17, 12)
			,new oyCrosswordClue(8, 
				"A way of notifying change to a number of classes", 
				"Observer", "582925e788eda44bdd66bb70b26b488d", 0, 11, 15)
			,new oyCrosswordClue(5, 
				"Alter an object's behavior when its state changes", 
				"State", "2c7c2e5c518ad7e1469073f6d5aa992a", 1, 9, 1)
			,new oyCrosswordClue(8, 
				"Encapsulates an algorithm inside a class", 
				"Strategy", "30a9b3595abdedff29f2aa6a391b560e", 0, 9, 1)
			,new oyCrosswordClue(8, 
				"This method defers the exact steps of an algorithm to a subclass", 
				"Template", "550857c3398f86050a550e250cf19b7e", 0, 11, 17)
			,new oyCrosswordClue(7, 
				"Defines a new operation to a class without change", 
				"Visitor", "1e5fe27c47649dddeb7ed2b2d86bb583", 1, 5, 13)
		  ],
		  20,
		  20
		);
	
	--></script>

Configuring puzzle involves creating instances of oyCrosswordPuzzle and oyCrosswordClue JavaScript objects. Let's see what parameters are required for each.

function oyCrosswordPuzzle(
	guid,	// universal identifier for this puzzle 
		// in your system, i.e. "12345"
	home,	// relative location of js libraries and image files 
		// relative to the main HTML file where puzzle is embedded, 
		// i.e. "./oy-cword-1.0"
	ns,	// think of it as of your own 'cookie'; you set it and it 
		// is carried all the way to the server when the move is submitted, i.e. "67890"
	title,	// title of the puzzle, i.e. "World's Best Puzzle"
	desc,	// description of the puzzle, i.e. "This is for all puzzle lover's out there..."
	clues,	// array of oyCrosswordClue objects for this puzzle
	w,	// play area width in cells, i.e. "20"
	h	// play area height in cells, i.e. "20"
){...}

function oyCrosswordClue(
	len,		// length of the word in symnbols, i.e. for the word 
			// "Abstract" this will be 8
	clue,		// the text of the word clue given to the user, i.e. 
			// for the word "Abstract" this will be "This factory 
			// creates an instance of several families of classes"
	answer,		// thw word itself, i.e. "Abstract"; maybe be ommited, 
			// thus disabling the reveal function
	sign,		// MD5 signature of the word itself with puzzle uid, 
			// i.e. for the word "Abstract" and uid "5748185539682739085" 
			// this will be "26f265b96e01081a5ef26a432eda9cff"
	dir,		// word direction; 0 for horizontal and 1 for vertical
	xpos,		// zero-based coordinate of the word on X axis, zero on the left, 
			// i.e. for the word "Abstract" this will be 12
	ypos		// zero-based coordinate of the word on Y axis, zero at the top, 
			// i.e. for the word "Abstract" this will be 6
){...}

Configure the Crossword Player Behavior

Now it's time to customize the crossword player behavior. We can specify publisher information and control whether or not server hassupport for tracking actions and maintaining scores. Many more things can be customized, but you have to look at the source code.

	
<script type="text/javascript"><!--
		
	//
	// here we configure puzzle options, callbacks and publisher information
	// 
		 
	// publisher information
	oygCrosswordPuzzle.publisherName = "by Pavel Simakov";
	oygCrosswordPuzzle.publisherURL = "http://www.softwaresecretweapons.com";

	// game exit URL
	oygCrosswordPuzzle.leaveGameURL = "http://www.cnn.com";  
      
	// this is how to turn off server support; score submission and action tracking will be disabled
	oygCrosswordPuzzle.canTalkToServer = false;
	
--></script>

Configure the Server for Action Tracking and High Scores

There are couple of things you may want to on the server side: track actions and record scores. Look at the file submitScore.php and trackaCtion.php files in the /app directory of the ZIP file above. If

Here is a Crossword Player, Where is a Crossword Weaver?

Keep in mind that I only present here a Crossword player, not the Crossword weaver. You may need to use third party (likely commercial) Crossword weaver to create custom crosswords. I tried to develop a weaver, but it turned out to be very hard problem. Till date I did not have time to solve it myself, but there is someone who did. His name is Franz Korntner.

In 1996 Franz participated in Programmer of the Month (POM) competition. His crossword weaver jigsaw was a winner of the Crozzle POTM challenge. The c source code of Franz's entry can be found in the file jigsaw.c. You can compile it yourself quite easily on any Linux box like this:

$ gcc -o jigsaw.exe jigsaw.cc

The code works very well and is very tight and heavy on recursion. Given a list of words it will tightly pack them onto a crossword on the fixed size grid. I was able to understand most of it, but not quite enough for the rewrite. The problem is that we need to set the target grid size (10x10) before we start layout. So if it turns out that the words can't be layout out in the 10x10 grid, program stops instead of automatically increasing the grid size.

Instead of trying to fix the c code, which is difficult, I used different approach. I simply compiled different versions of the program for the different hard-coded grid sizes. When trying to layout the crossword, we would try to layout in the smallest grid. If fails, we try larger grid and so on until success. Horrible solution, but it works... This is how I created the original Gang Of Four Software Design Patterns Java Script Crossword.

Marc Kummel, another participant of POTM competition, has wrote his crossword weaver in BASIC. His program XWORD.BAS took 6th place overall. I have a copy here, and it runs on my Windows XP box just fine.

We start by scoring each word for how many letters it shares with other words in the list, and then we normalize the scores for the word length. The alignment starts with a high-scoring medium-length word, which we put near a corner, and then we build a linked list of crossed-words from that. If it's the best so far, we remember it. Then we try another one, and so on. It doesn't get any smarter as it works longer, but it randomly breaks its own rules now and then.

Almost Ready Weaver

There is another great crossword weaver by Michael Johnson. It is written in JavaScript itself and runs surprisingly fast. See how it lays out Gang Of Four words here. Look at the source of the HTML document to see how it is done.

While it works great, there is an issue that this algorithm often leaves some words orphaned and not connected to the rest of the word structure. Refresh the page several times and it eventually finds a fully-connected layout. Let's hope Michael finds fix for this problem soon so both the crossword weaver and the crossword player are both open-sourced in JavaScript.

The Final Word

Hundreds of people sent their positive notes about my JavaScript Crossword Engine. Thank you!

It is a real pleasure to have customers that love your product! Trust me.

Comments (13)

  • Comment by Sean — December 29, 2009 @ 10:38 pm

    Hi Pavel,
    Thank you so much for your kind release of the great JS Crossword Engine, I am only to happy to donate a token gift via PayPal and support your great work, and hope others do as well.
    I am really excited to now be able to play with the Crossword Engine even further, in many respects.
    Best wishes and regards,
    Sean :]

  • Comment by Janne — December 30, 2009 @ 4:14 pm

    What a great Christmas gift! Enormous thanks for this generous act, I hope I can afford to make a donation after graduating :)

  • Comment by karen — January 26, 2010 @ 11:50 pm

    hello…may i borrow your program for our project??…this will served as my basis to make my crossword puzzle…plssss…

    thanks and God bless

  • Comment by tj — June 14, 2010 @ 8:19 pm

    why do we need md5 hash? also, i tried generating your example and got a different hash.
    what code are you calling to generate?
    md5(Abstract5748185539682739085)?

  • Comment by Skippy — September 7, 2010 @ 3:28 pm

    Hello, I am making use of your fabulous crossword engine for an open source project I work with eFront. Thank you for making it available. I ran into an issue I'm wondering if you have run across before. I'm trying to use non english (ie. unicode / utf-8) such as chinese (坦率) or greek (ειλικρινής) and the engine does not seem to like it at all. Have you worked with, or known of anyone that has worked with, getting this sorted out? Thanks in advance! Skippy

  • Comment by Pablo — January 29, 2012 @ 12:07 pm

    As you can do to make the words are written vertically from bottom to top

  • Comment by Parappa — February 7, 2012 @ 11:19 am

    Your crossword program looks really great! I tried to create a custom crossword riddle with it by simply modifying the oyCrosswordPuzzle and oyCrosswordClue functions, but I don't get it to work at all… It does not display the puzzle at all and keeps on showing the "Starting up…" message. Is it possible to do it this way at all? What am I doing wrong?

  • Comment by Tony — September 4, 2012 @ 3:52 am

    @skippy, I have modified it successfully to work in TAMIL, utf-8. The key is to drop the answers, (also the chekc/reveal) which is anyway a fancy item compared to newspapers revealing it on next day. Then modifying text capturing and making it work in your language too.

  • Comment by Tony — September 4, 2012 @ 3:54 am

    @pablo, during clue list rendering, and on putting image on the clues, you need to re-order it to your liking, and you will need better programming skill in JScript for that, see my website, the order is much better in that version even though thats not english,

  • Comment by Tony — September 4, 2012 @ 3:57 am

    Ok, my working solution is actually here and (on this comment click on the name), and the other two are where I have problems with cascading two-three css.

  • Comment by ??? - ????? ????????? — September 21, 2012 @ 11:01 am

    Dear Pavel,

    I would like to let you know that I have successfully used it for TAMIL language, using english keyboard-phonetic typing, and it works great!. Also I changed the ordering so that it numbers 1,2,3 from top left to bottom right.

  • Comment by Elke — May 9, 2013 @ 3:28 pm

    I love the script and it works great in Firefox, but I'm seeing almost half a grid of an unidentified row on the right in IE (9) once I start the crossword and the clues appear. Any ideas what this might be?

  • Comment by chris harding — December 5, 2013 @ 4:13 am

    Hi, thanks for this it is great!

    One problem, if a answer has a space in, the check answer does not work it says it is incomplete and shows a full stop rather than a space?

    How do I fix this?


Leave a comment


  Copyright © 2004-2014 by Pavel Simakov
any conclusions, recommendations, ideas, thoughts or the source code presented on this site are my own and do not reflect a official opinion of my current or past employers, partners or clients