Archive for October, 2005

Java Word Search Puzzle Generator

Java No Comments »

Thought I’d start off with something simple.

Today’s code sample is a Java application which generates random wordsearch puzzles given a list of words in a file. It is a command-line application which is run like this (I’ll leave it up to you to figure out how to compile the application):


java -cp . WSGen <width> <height> <wordlistfile> <outfile>

The <width> and <height> parameters are the width and height of the word search puzzle, in characters. The <wordlistfile> parameter is the filename of a text file which contains words to include in the word search puzzle. The file should be a plain text file containing one word per line. The generator will use as many words as possible from this file, until it can’t fit any more words in the puzzle. The <outfile> parameter is the name of the output file into which the resulting puzzle will be saved. I typically end it with a “.wsp” extension, which stands for “Word Search Puzzle”. The output file will be in the following format (this is an example):


grid:15,15
word:10,14,6,CADILLAC
word:3,10,0,HUMMER
word:11,14,0,SUZUKI
word:11,8,5,SATURN
word:1,13,0,VOLKSWAGEN
word:2,7,4,DODGE
word:12,7,4,PONTIAC
word:0,2,0,KIA
word:3,12,1,MERCEDESBENZ
word:6,4,6,NISSAN
word:5,11,1,HYUNDAI
word:9,4,5,TOYOTA
word:0,5,4,MINI
word:13,12,0,MITSUBISHI
word:14,12,0,ISUZU
word:7,0,2,MERCURY
word:4,7,4,SAAB
word:0,12,0,BMW
word:5,12,1,HONDA
word:14,0,5,VOLVO
word:12,2,6,LANDROVER
word:7,5,6,SUBARU
word:7,11,1,SCION
word:6,1,2,FORD
word:12,12,7,AUDI
word:6,3,2,ACURA
word:1,0,2,BUICK
word:14,7,0,JAGUAR
word:10,13,6,INFINITI
word:1,1,2,LEXUS
word:5,3,6,JEEP
word:9,5,5,MAZDA
word:10,7,1,GMC
fillchars:ZYWXPZMMIVKOXVHPVQEBMLPTHIVWWPOWHCBPOXX

The first line (”grid:”) defines the grid width and height.
Each line that begins with “word:” contains a word’s position and orientation, and the word itself. The position is in x (horizontal), y (vertical) format. The orientation is one of the following:

  • 0 = up
  • 1 = up/right
  • 2 = right
  • 3 = down/right
  • 4 = down
  • 5 = down/left
  • 6 = left
  • 7 = up/left

The last line (”fillchars:”) contains exactly enough characters to fill the remaining unfilled character spaces in the grid, going from left to right, top to bottom.

Here is the main class, WSGen.java:

// Copyright (c) 2005 Ronald B. Cemer
// All rights reserved worldwide.
	
import java.io.*;
import java.util.*;
	
public class WSGen
    implements WSWordOrientation {
    public static final int MIN_WIDTH_HEIGHT = 8;
    public static final int MIN_WORD_LENGTH = 3;
	
    private int gridWidthChars, gridHeightChars;
    private char[][] grid;        // [y][x]
    private Random rand = new Random(System.currentTimeMillis());
	
    public WSGen(int gridWidthChars, int gridHeightChars) {
        if ( (gridWidthChars < MIN_WIDTH_HEIGHT) || (gridHeightChars < MIN_WIDTH_HEIGHT) ) {
            throw new Error("Width and height must each be at least "+MIN_WIDTH_HEIGHT+".");
        }
        this.gridWidthChars = gridWidthChars;
        this.gridHeightChars = gridHeightChars;
        grid = new char[gridHeightChars][gridWidthChars];
        for (int y = 0; y < gridHeightChars; y++) {
            for (int x = 0; x < gridWidthChars; x++) {
                grid[y][x] = ‘ ‘;
            }
        }
    }
	
    public void process(File wordFile, File outFile)
        throws IOException {
	
        process(wordFile, outFile, true);
    }
	
    public void process(File wordFile, File outFile, boolean fillUnusedCellsWithRandomCharacters)
        throws IOException {
	
        List sourceWordList = readWordList(wordFile);
        PrintWriter out = new PrintWriter(new OutputStreamWriter(new FileOutputStream(outFile)));
        try {
            out.print("grid:");
            out.print(gridWidthChars);
            out.print(",");
            out.println(gridHeightChars);
	
            int failuresInARow = 0;
            while ( (sourceWordList.size() > 0) && (failuresInARow < 100) ) {
                int wordIdx = (rand.nextInt()&0×7fffffff)%sourceWordList.size();
                String testWord = (String)sourceWordList.get(wordIdx);
                if (fitWord(out, testWord)) {
                    failuresInARow = 0;
                } else {
                    failuresInARow++;
                }
                sourceWordList.remove(wordIdx);
            }
///
///printGrid();
///System.out.println();
            if (fillUnusedCellsWithRandomCharacters) {
                fillUnusedCellsWithRandomCharacters(out);
///
///printGrid();
///System.out.println();
            }
            out.flush();
            out.close();
            out = null;            // Setting out to null indicates successful completion.
        } finally {
            if (out != null) {
                // Not successful because out != null.
                try { out.close(); } catch(Exception ex) {}
                outFile.delete();
            }
        }
    }
	
    public void fillUnusedCellsWithRandomCharacters(PrintWriter out)
        throws IOException {
	
        out.print("fillchars:");
        for (int y = 0; y < gridHeightChars; y++) {
            for (int x = 0; x < gridWidthChars; x++) {
                if (grid[y][x] == ‘ ‘) {
                    char c = "ABCDEFGHIJKLMNOPQRSTUVWXYZ".charAt((rand.nextInt()&0×7fffffff)%26);
                    grid[y][x] = c;
                    out.print(c);
                }
            }
        }
        out.println();
    }
	
    private List readWordList(File wordFile)
        throws IOException {
	
        BufferedReader in =
            new BufferedReader(new InputStreamReader(new FileInputStream(wordFile)));
        List wordList = new ArrayList();
        try {
            String s;
            int maxWordLength = Math.max(gridWidthChars, gridHeightChars);
            while ((s = in.readLine()) != null) {
                s = s.trim().toUpperCase();
                int len = s.length();
                if (len == 0) continue;                    // Empty line
                if (s.startsWith("#")) continue;    // Comment
                if (len < MIN_WORD_LENGTH) {
                    System.err.println(
                        "Tossing word “"+s+"” because it is too short (must be at least "+
                        MIN_WORD_LENGTH+" characters).");
                    continue;
                }
                if (len > maxWordLength) {
                    System.err.println(
                        "Tossing word “"+s+
                        "” because it is longer than the grid’s width and height.");
                    continue;
                }
                boolean hasNonLetters = false;
                for (int i = 0; i < len; i++) {
                    if (!Character.isLetter(s.charAt(i))) {
                        hasNonLetters = true;
                        break;
                    }
                }
                if (hasNonLetters) {
                    System.err.println(
                        "Tossing word “"+s+
                        "” because it contains one or more non-letter characters.");
                    continue;
                }
                if (wordList.indexOf(s) >= 0) {
                    System.err.println("Tossing duplicate word “"+s);
                    continue;
                }
                wordList.add(s);
            }
            return wordList;
        } finally {
            in.close();
        }
    }
	
    private boolean fitWord(PrintWriter out, String testWord)
        throws IOException {
	
        // Pick a random starting point and orientation.
        int x = (rand.nextInt()&0×7fffffff)%gridWidthChars;
        int y = (rand.nextInt()&0×7fffffff)%gridHeightChars;
        int orientation = (rand.nextInt()&0×7fffffff)%8;
        // Try to fit the word in every spot in the grid, starting at the random x, y position.
        // Each fit attempt must try all eight orientations.
        for (int yy = 0; yy < gridHeightChars; yy++, y = (y+1)%gridHeightChars) {
            for (int xx = 0; xx < gridWidthChars; xx++, x = (x+1)%gridWidthChars) {
                for (int oo = 0; oo < 8; oo++, orientation = (orientation+1)%8) {
                    if (fitWord(out, testWord, x, y, orientation)) {
                        return true;
                    }
                }
            }
        }
        return false;
    }
	
    private boolean fitWord(PrintWriter out, String testWord, int x, int y, int orientation)
        throws IOException {
	
        // Try to fit the word into the grid at the starting x,y position with the specified
        // orientation (0=up, 1=up/right, 2=right, 3=down/right, 4=down, 5=down/left, 6=left,
        // 7=up/left
        int xo = ORIENTATION_XY[orientation][0];
        int yo = ORIENTATION_XY[orientation][1];
        int len = testWord.length();
        // The fit attempt must fail if the word would run off of any edge of the grid.
        int xEnd = x+((len-1)*xo);
        int yEnd = y+((len-1)*yo);
        if ( (xEnd < 0) || (xEnd >= gridWidthChars) || (yEnd < 0) || (yEnd >= gridHeightChars) ) {
            return false;
        }
        // The fit attempt must fail if any character that exists in the grid is not a space and
        // does not match the character at that position in the word.
        // The fit attempt must fail if the entire word matches characters already in the grid
        // (no spaces encountered in the grid while placing the word).
        boolean allMatch = true;
        for (int i = 0, xx = x, yy = y; i < len; i++, xx += xo, yy += yo) {
            char wc = testWord.charAt(i);
            char gc = grid[yy][xx];
            if (gc != wc) {
                allMatch = false;
                if (gc != ‘ ‘) {
                    return false;
                }
            }
        }
        if (allMatch) {
            return false;
        }
        for (int i = 0, xx = x, yy = y; i < len; i++, xx += xo, yy += yo) {
            grid[yy][xx] = testWord.charAt(i);
        }
        out.print("word:");
        out.print(x);
        out.print(‘,’);
        out.print(y);
        out.print(‘,’);
        out.print(orientation);
        out.print(‘,’);
        out.println(testWord);
        return true;                // success
    }
	
    private void printGrid() {
        for (int y = 0; y < gridHeightChars; y++) {
            for (int x = 0; x < gridWidthChars; x++) {
                if (x > 0) System.out.print(‘ ‘);
                System.out.print(grid[y][x]);
            }
            System.out.println();
        }
    }
	
    public static void usage() {
        System.err.println
            ("Usage:");
        System.err.println
            ("java "+WSGen.class.getName()+" <width> <height> <wordListFile> <outFile>");
    }
	
    public static int process(String[] args) {
        if (args.length != 4) {
            usage();
            return 1;
        }
        try {
            int gridWidthChars = Integer.parseInt(args[0].trim());
            int gridHeightChars = Integer.parseInt(args[1].trim());
            if ( (gridWidthChars < MIN_WIDTH_HEIGHT) || (gridHeightChars < MIN_WIDTH_HEIGHT) ) {
                System.err.println("Width and height must each be at least "+MIN_WIDTH_HEIGHT+".");
                usage();
                return 1;
            }
            File wordFile = new File(args[2]);
            File outFile = new File(args[3]);
            new WSGen(gridWidthChars, gridHeightChars).process(wordFile, outFile);
        } catch(Exception ex) {
            ex.printStackTrace();
            return 99;
        }
        return 0;
    }
	
    public static void main(String[] args) {
        System.exit(process(args));
    }
}
	

And here is the class which contains the constants which are used to determine the word orientations, WSWordOrientation.java:

// Copyright (c) 2005 Ronald B. Cemer
// All rights reserved worldwide.
	
public interface WSWordOrientation {
    public static final int[][] ORIENTATION_XY = {
        {  0, -1 },      // up
        {  1, -1 },      // up/right
        {  1,  0 },      // right
        {  1,  1 },      // down/right
        {  0,  1 },      // down
        { -1,  1 },      // down/left
        { -1,  0 },      // left
        { -1, -1 }      // up/left
    };
}
	

Note that this is a separate class so that any player applet you may develop can determine word direction based on word orientation because the inner arrays are delta X, delta Y for each character increment within the word.

If you find this code useful, drop me a line at ron at roncemer dot com.

Enjoy!

Introduction

Software Development Technical Articles No Comments »

Hi, my name is Ron. Nice to meet you.

I’ve been writing software professionally for about 18 years now. I’ve seen more technologies come and go than you could shake a stick at.

Over the years, I’ve developed a rather sizeable library of code written in various languages. Some of my stuff is experimental in nature, but a lot of it is in use in real-world, mission-critical apps that are used by many people every day.

I’ve developed all kinds of software, including telephony apps, air and ground vehicle simulation software (including flight simulation), accounting/POS/inventory control (ERP) software, embedded control applications, imaging software, database-driven apps (including highly scalable web apps and web services), video surveillance and monitoring systems, document imaging/storage/retrieval systems, OCR algorithms, image/audio/video compression and streaming software, signal processing algorithms, 3D animation/virtual reality applications, stock market data aggregation/distribution systems, real-time/web-based stock charting systems, subscription-based web sites, web-based advertising software (banner ad networks), and the list goes on.

I say this not to boast, but to introduce myself and to pique your interest. In the future, I plan to post some of my experiences onto this weblog in the hope that it will be useful to you, the reader. This weblog will serve as a sort of “clearing house” of information. I’ll post my ideas, thoughts and comments on various technologies that I’ve worked with, and share with you my tips and tricks that I’ve learned by working with the technologies I cover.

When it comes to programming languages, I’m mostly a Java guy, but prior to that I was a C/C++ guy for many years. I just like Java better because it’s easier to get things done. You don’t have to worry about leaking memory or accidentally freeing a block of memory more than once. If you overrun an array, you get an Exception instead of silently trashing something else in memory.

For the past year, along with heavy Java usage, I’ve also done quite a bit of intranet and Internet application development in .NET (mainly C#, but some VB.NET). This includes co-developing a banner ad serving system for my employer.

When it comes to operating systems, I’m primarily a Linux guy, but I have significant experience with other UNIX-based OSes, some experience with Mac OS, and of course the dreaded Windows. I’ve been using Linux consistently since 1993. I started with Slackware Linux, circa version 1.2??? or so, I can’t remember for sure. When Red Hat became really popular in the late 90s, I switched to Red Hat Linux 4.0. Since then, I’ve pretty much stayed with Red Hat, through all of the major releases (and many of the minor releases), except that I skipped Red Hat 9.0. Bot never fear, I’ve used Red Hat 9 Enterprise WS at work. Most recently, I’ve been using Fedora Core 4 (started with FC1 and have used each of the releases since). I love Fedora (also from Red Hat) because it’s a little more bleeding-edge than the mainline Red Hat stuff. So you get more toys to play with.

My UNIX experience goes back to 1989. I started with AT&T System V on a box made by a company that’s no longer in business. After that, I spent several years working on SCO Open Server 5. A short trial of Coherent UNIX proved that it was not ready for prime time. The company went out of business around 1995, and released the source code to that operating system, which you can download here. I’ve developed on IBM AIX, SGI IRIX, and spent a few years developing on (and operating a server farm running) Solaris 8 and 9 from Sun Microsystems. Some UNIXes (UNICES???) are very flaky (SCO, for example - think “kernel panic on a regular basis, and without cause”), but most are incredibly stable. Solaris is one of my more favorite UNIXes due to its stability and incredible scalability (it has an excellent threading model). Linux, I believe, has since caught up with it, though.

My latest (and hottest) area of interest is currently LAMP (Linux, Apache, PHP and MySQL). PHP is a fabulous language. It’s sort of a cross between C and shell scripting. PHP uses dollar signs to reference variable values, which reminds me of shell scripting. Most of the built-in functions are named after their counterparts in the standard C library, which makes it incredibly friendly to the veteran C programmers out there (including myself). The language syntax is sort of a mix of C, JavaScript, C++ and Java (pretty much in that order). However, PHP 5 (which is the only version of PHP I would care to mess around with) is also an Object-Oriented language. You can go procedural, O-O, or a combination of both. And it’s incredibly fast, from the point of view of performance, yes, but also from the development point of view. It’s just incredibly easy to edit the page, save it, and then just refresh the browser and instantly see the results. I’ve been using PHP and MySQL on Apache (running on Linux, of course) to develop a new banner ad serving platform at work, and it’s done great. I tossed memcached into the mix in order to achieve the scalability and performance goals, and the project just turned out wonderfully. You know, I’ve done projects using Enterprise JavaBeans (EJBs) to separate out the business logic, and I’ve played around with all kinds of persistence frameworks, presentation frameworks, etc., but I’ll tell you one thing: If you really want scalability and performance, it’s hard to beat the LAMP stack. The more I use this incredibly simple platform, the more I love it. PHP compiles nearly instantly, and if you use APC, your pages are bytecode-cached automatically so they run pretty much as fast as native code.

I hope this is enough to get your interest piqued. I’ll be posting more soon!

Ron

This site is best viewed with Firefox 1.5 or later.
Upgrade to Firefox 1.5!