by Pavel Simakov 2009-12-29
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 (GOF) 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>
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.